CleverCache 1.0.8
See the version list below for details.
dotnet add package CleverCache --version 1.0.8
NuGet\Install-Package CleverCache -Version 1.0.8
<PackageReference Include="CleverCache" Version="1.0.8" />
<PackageVersion Include="CleverCache" Version="1.0.8" />
<PackageReference Include="CleverCache" />
paket add CleverCache --version 1.0.8
#r "nuget: CleverCache, 1.0.8"
#:package CleverCache@1.0.8
#addin nuget:?package=CleverCache&version=1.0.8
#tool nuget:?package=CleverCache&version=1.0.8
CleverCache
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
Register the services:
builder.Services.AddCleverCache();
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 }); } }
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 | Versions 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. |
-
net9.0
- MediatR (>= 12.5.0)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.3.0)
- Microsoft.EntityFrameworkCore (>= 9.0.2)
- Microsoft.Extensions.Caching.Memory (>= 9.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.