-
Notifications
You must be signed in to change notification settings - Fork 1
Migrating to V2
V2 is a significant refactor. The core caching API is mostly the same but several breaking changes tighten the design and reduce coupling. This page documents every change with before/after examples.
| Area | V1 | V2 |
|---|---|---|
| EF Core dependency | Bundled in CleverCache
|
Separate CleverCache.EntityFrameworkCore package |
| Startup (interceptor) |
AddCleverCache() registered interceptor |
AddCleverCacheEntityFramework() required |
| Startup (scanning) | app.UseCleverCache<TContext>() |
app.ScanDbSetsForCacheDependencies<TContext>() |
| Attribute discovery |
UseCleverCache picked up [DependantCaches] automatically |
Must call ScanAssemblyContaining<T>() explicitly in AddCleverCacheEntityFramework
|
| Scan options | CleverCacheOptions.Scanning |
Passed directly to ScanDbSetsForCacheDependencies
|
GetOrCreate factory |
Func<ICacheEntry, TItem> |
Func<TItem> |
GetOrCreateAsync factory |
Func<ICacheEntry, Task<TItem>> |
Func<Task<TItem>> |
| Cache entry options type | MemoryCacheEntryOptions |
CleverCacheEntryOptions |
| Dependent cache attribute | [DependantCaches] |
[DependentCaches] |
FakeCache namespace |
CleverCache.Implementations |
CleverCache |
DependentCacheNavigationScanMode namespace |
CleverCache.Models |
CleverCache.EntityFrameworkCore.Models |
V2 moves all EF Core concerns into a dedicated package. Your main project no longer depends on Microsoft.EntityFrameworkCore.
Before — one package does everything:
dotnet add package CleverCache
After — install both:
dotnet add package CleverCache
dotnet add package CleverCache.EntityFrameworkCore
If you're using MediatR auto-caching via [AutoCache], also install the separate MediatR integration package:
dotnet add package CleverCache.MediatR
[AutoCache] and [InvalidatesCache] live in CleverCache.MediatR in V2. Keep CleverCache for the core cache API, and add CleverCache.MediatR for MediatR pipeline attributes/behaviours. For full setup, see MediatR Integration.
AddCleverCache() no longer registers the EF Core interceptor. Replace both calls with a single AddCleverCacheEntityFramework(), which registers the interceptor and calls AddCleverCache() internally.
Before:
builder.Services.AddCleverCache();After:
builder.Services.AddCleverCacheEntityFramework();
// with CleverCache options (e.g. assembly scanning):
builder.Services.AddCleverCacheEntityFramework(o => o.ScanAssemblyContaining<Order>());If you are not using EF Core (manual invalidation only), keep using
AddCleverCache()—AddCleverCacheEntityFrameworkis only needed if you have the EF package installed.
Only needed if you were using navigation scanning. If you used
UseCleverCachepurely to pick up[DependantCaches]attributes, you can simply remove the call — attribute registration is now handled separately in step 5.
If you were using navigation scanning, replace app.UseCleverCache<TContext>() with app.ScanDbSetsForCacheDependencies<TContext>(). Scan options are now passed directly instead of being stored on CleverCacheOptions.
Before:
builder.Services.AddCleverCache(o =>
{
o.Scanning.NavigationScanMode = DependentCacheNavigationScanMode.Direct;
o.Scanning.ReverseNavigationDependencies = true;
});
app.UseCleverCache<AppDbContext>();After:
builder.Services.AddCleverCacheEntityFramework();
// ...
app.ScanDbSetsForCacheDependencies<AppDbContext>(o =>
{
o.NavigationScanMode = DependentCacheNavigationScanMode.Direct;
o.ReverseNavigationDependencies = true;
});Multiple DbContexts each get their own call with independent options:
app.ScanDbSetsForCacheDependencies<AppDbContext>();
app.ScanDbSetsForCacheDependencies<ReportingDbContext>(o =>
o.NavigationScanMode = DependentCacheNavigationScanMode.Recursive);Note:
ScanDbSetsForCacheDependenciesis navigation scanning only — it no longer processes[DependentCaches]attributes. See step 5 below.
The attribute class was renamed to fix a spelling mistake. The navigationScanMode parameter has been removed — navigation scanning is now exclusively handled by ScanDbSetsForCacheDependencies.
Before:
[DependantCaches([typeof(OrderLine), typeof(OrderNote)])]
public class Order { }After:
[DependentCaches([typeof(OrderLine), typeof(OrderNote)])]
public class Order { }In V1, UseCleverCache processed [DependantCaches] attributes as a side effect of EF model scanning. In V2 these are separate concerns — ScanDbSetsForCacheDependencies handles navigation scanning only. Attributes must now be registered explicitly via ScanAssemblyContaining:
builder.Services.AddCleverCacheEntityFramework(o => o.ScanAssemblyContaining<Order>());You can combine both: use ScanAssemblyContaining for explicit attribute-based dependencies and ScanDbSetsForCacheDependencies for navigation-driven discovery.
The factory delegate no longer receives an ICacheEntry. Cache entry options are now a separate parameter of type CleverCacheEntryOptions instead of MemoryCacheEntryOptions.
Before:
var result = await cache.GetOrCreateAsync<List<Order>>(
[typeof(Order)],
"orders-all",
async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
return await db.Orders.ToListAsync();
});After:
var result = await cache.GetOrCreateAsync<List<Order>>(
[typeof(Order)],
"orders-all",
async () => await db.Orders.ToListAsync(),
new CleverCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });The synchronous overload changes identically:
Before:
var result = cache.GetOrCreate<int>(
[typeof(Order)],
"order-count",
entry => db.Orders.Count());After:
var result = cache.GetOrCreate<int>(
[typeof(Order)],
"order-count",
() => db.Orders.Count());FakeCache has moved from the CleverCache.Implementations namespace to the root CleverCache namespace.
Before:
using CleverCache.Implementations;
mocker.Use<ICleverCache>(new FakeCache());After:
// No extra using needed — FakeCache is in the CleverCache namespace
mocker.Use<ICleverCache>(new FakeCache());V1 required you to inject IInterceptor or CleverCacheInterceptor into your DbContext constructor. This still works in V2 (the interceptor is still registered as IInterceptor), but the preferred V2 pattern is to wire it up in AddDbContext using the extension method:
V1 (still valid in V2 — no change required):
public class AppDbContext(IInterceptor cleverCacheInterceptor) : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.AddInterceptors(cleverCacheInterceptor);
}V2 preferred (no DbContext constructor changes):
// Program.cs
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
options.UseSqlServer(connectionString);
options.AddCleverCache(sp); // extension from CleverCache.EntityFrameworkCore
});
// AppDbContext.cs — no interceptor injection needed
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options) { }These aren't breaking changes but are worth knowing about:
-
ScanAssemblyContaining<T>()— scan assemblies for[DependentCaches]attributes without EF Core. Replaces manually callingAddDependentCachefor each type. -
[InvalidatesCache]for MediatR — decorate any command with[InvalidatesCache(typeof(Order))]to evict cache entries automatically after the command succeeds. -
InvalidateCachesbulk extension — chain ontoExecuteDelete/ExecuteDeleteAsyncresults for bulk operations that bypass the change tracker. -
Distributed cache and Redis —
CleverCache.Redisis now the only way to bring in StackExchange.Redis. TheAddCleverCache()options no longer acceptUseRedisCache. -
Custom cache provider — implement
ICleverCacheStoreand register withAddCleverCache(o => o.UseCustomStore<MyStore>()). -
Stampede protection — concurrent requests for the same key now only invoke the factory once.