CleverCache 1.0.8

Additional Details

There is a newer version of this package available.
See the version list below for details.
dotnet add package CleverCache --version 1.0.8
                    
NuGet\Install-Package CleverCache -Version 1.0.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CleverCache" Version="1.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CleverCache" Version="1.0.8" />
                    
Directory.Packages.props
<PackageReference Include="CleverCache" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CleverCache --version 1.0.8
                    
#r "nuget: CleverCache, 1.0.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CleverCache@1.0.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CleverCache&version=1.0.8
                    
Install as a Cake Addin
#tool nuget:?package=CleverCache&version=1.0.8
                    
Install as a Cake Tool

CleverCache

NuGet NuGet

CleverCache was designed to try and solve the problem having to remember (or know when) to invalidate cache entries when the data in them is out of date. This often particularly hard when cache entries contain data from multiple entities a change in any of them effectively means the cached data is now wrong. Trying to do this manually often causes cross-cutting concerns and invariably we forget something important which proves to be a right pain in the butt.

With a small amount of configuration CleverCache will automatically track changes in your database context and reset the cache for any entity if an entity of that type is create, updated or deleted, and - if required, any related entity where data is also part of the same cache entry.

BONUS: If you're using Mediatr, CleverCache can automatically cache results but using a pipeline behaviour with minimal changes to your existing code.

Installing CleverCache

You should install CleverCache with NuGet:

Install-Package CleverCache

Or via the .NET Core command line interface:

dotnet add package CleverCache

Either commands, from Package Manager Console or .NET Core CLI, will download and install CleverCache and all required dependencies.

Get Started

  1. Register the services:

    builder.Services.AddCleverCache();
    
  2. Ensure the interceptor is registered on your database context in any of the following ways:

    // The interceptor interface if you have no other interceptors
    public class AppDbContext(IInterceptor cleverCacheInterceptor) : DbContext()
    {
        private readonly IInterceptor _cleverCacheInterceptor = cleverCacheInterceptor;
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.AddInterceptors(new IInterceptor[] { cleverCacheInterceptor });
        }
    }
    

    or

    // The interceptor array if you are already using interceptors
    public class AppDbContext(IInterceptor[] interceptors) : DbContext()
    {
        private readonly IInterceptor[] _interceptors= interceptors;
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.AddInterceptors(interceptors);
        }
    }
    

    or the concrete class

    // The interceptor interface if you have no other interceptors
    public class AppDbContext(CleverCacheInterceptor cleverCacheInterceptor) : DbContext()
    {
        private readonly CleverCacheInterceptor _cleverCacheInterceptor = cleverCacheInterceptor;
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.AddInterceptors(new IInterceptor[] { cleverCacheInterceptor });
        }
    }
    
  3. Add the using to your app specifying the db context you are tracking:

    app.UseCleverCache<AppDbContext>();
    

Usage

You create cache in the same way you would when using MemoryCache, but specify an additional type parameter as shown below to associate a given type with a cache key:

    var myItem = await cache.GetOrCreateAsync(
	    typeof(MyEntityType),
	    cacheKey,
	    _ => 
	    {
		    //return <Do real query for data>;
	    }
    ) ?? [];

The interceptor tracks when any instance of MyEntityType is added, changed or deleted and will clear all cache keys associated with that type.

Dependent Caches

Often you have information in a cache entry that contains data from multiple entity types and the caches needs to be refreshed if ANY of the types changes not just the primary object.

tl;dr: See the DependantCaches attribute below

You can create these associations manually on a type by type basis by calling:

cache.AddKeyToType(type, key);

or more succinctly:

cache.AddKeyToType<OtherType>(key);

You can also do multiple types in one call by doing:

cache.AddKeyToTypes(arrayOfTypes, key);

You can also do it by specifying an array of types when calling any of the create methods.

However, this can be tiresome and result in repetitive code. If you know you often need to do this for a given entity you can configure it globally via an attribute on the entity class like this:

[DependantCaches([typeof(ThingTwo),typeof(ThingThree)])]
public class ThingOne 
{
    public ThingTwo Two {get; set;};
    public ThingThree Three {get; set;};
}

public class ThingTwo;
public class ThingThree;

This will automatically register any keys for ThingOne with ThingTwo and ThingThree so changes to any object of these types will clear the cache key. You can also reverse these mappings by using reverse: true in the attribute. This will register ThingTwo and ThingThree with ThingOne

Auto caching mediatr queries

This is a really powerful tool that enables you to quickly add caching to your mediatr queries without any changes to your handlers.

Add the following to your mediatr setup:

services.AddMediatR(cfg =>
{
	// Other config you may have
	cfg.AddCleverCache(); // Registers the mediatr pipeline behaviour
});

Then simply add the following attribute to any query you want to cache, specifing the type(s) you want the cache for:

[AutoCache([typeof(MyEntityType)])]
public record MyQuery : IRequest;

This uses the mediatr request as the cache key so you can use the same query with different parameters and it will cache each one separately.

Unit testing

Unit testing methods that use cache is generally fiddly, to help with this CleverCache is shipped with a FakeCache implementation which you can use in your test. The implementation never caches and always calls your underlying method retrieve your data. For example when using Moq.AutoMocker you would do this:

var mocker = new AutoMocker();
mocker.Use<ICleverCache>(new FakeCache());
var sut = mocker.CreateInstance<CarServiceWithCache>();

// Run unit tests as normall
var result = sut.GetDoorCount();

Now can unit test the GetDoorCount method without the cache getting in the way.

Note: If you're using the Mediatr automatic caching you don't need this.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.