-
Notifications
You must be signed in to change notification settings - Fork 0
Programmatic API
ICacheWeaveService provides direct cache access for scenarios where attribute decoration is not possible — service-layer code, background jobs, event handlers, or complex conditional logic.
public interface ICacheWeaveService
{
Task<T?> GetOrSetAsync<T>(
string key,
Func<CancellationToken, Task<T?>> factory,
TimeSpan? expiry = null,
CancellationToken ct = default);
Task<T?> GetAsync<T>(
string key,
CancellationToken ct = default);
Task SetAsync<T>(
string key,
T value,
TimeSpan? expiry = null,
CancellationToken ct = default);
Task InvalidateAsync(
string key,
CancellationToken ct = default);
Task InvalidateByPrefixAsync(
string prefix,
CancellationToken ct = default);
Task InvalidateByPrefixesAsync(
IEnumerable<string> prefixes,
CancellationToken ct = default);
}ICacheWeaveService is registered automatically by AddCacheWeave. Inject it anywhere in your DI graph:
public class ProductService(ICacheWeaveService cache, AppDbContext db)
{
...
}The primary method. Returns the cached value if present; otherwise executes the factory, writes the result to the cache, and returns it.
The factory receives the CancellationToken so downstream async calls can be cancelled correctly:
var products = await cache.GetOrSetAsync<List<ProductDto>>(
key: "products:list:page=1",
factory: async ct => await db.Products
.OrderBy(p => p.Name)
.Take(20)
.Select(p => new ProductDto(p))
.ToListAsync(ct),
expiry: TimeSpan.FromMinutes(5));Return null from the factory to skip writing to the cache:
var result = await cache.GetOrSetAsync<PagedResult<ProductDto>>(
key: "products:list:page=1",
factory: async ct =>
{
var items = await db.Products.Take(20).ToListAsync(ct);
// Don't cache empty pages
return items.Count > 0 ? new PagedResult<ProductDto>(items) : null;
},
expiry: TimeSpan.FromMinutes(5));GetOrSetAsync is protected by ICacheStampedeProtector. Concurrent calls with the same key will only execute the factory once. See Stampede Protection.
Read-only cache lookup. Returns null on a miss.
var cached = await cache.GetAsync<ProductDto>("products:detail:abc123");
if (cached is null)
{
// Cache miss — fetch from DB
}Write a value directly to the cache, bypassing the factory pattern.
await cache.SetAsync(
key: "products:detail:abc123",
value: productDto,
expiry: TimeSpan.FromMinutes(10));Useful after a write operation when you want to pre-populate the cache with the known new value rather than waiting for the next read to re-fetch it.
public async Task<ProductDto> CreateAsync(CreateProductCommand cmd)
{
var product = await db.Products.AddAsync(new Product(cmd));
await db.SaveChangesAsync();
var dto = new ProductDto(product.Entity);
// Pre-populate the detail cache
await cache.SetAsync($"products:detail:{dto.Id}", dto, TimeSpan.FromMinutes(10));
// Invalidate the list cache
await cache.InvalidateByPrefixAsync("products:list:");
return dto;
}Remove a single cache entry by exact key.
await cache.InvalidateAsync("products:list:page=1");Remove all cache entries whose key starts with the given prefix.
await cache.InvalidateByPrefixAsync("products:");Requires a provider that supports prefix removal (Redis, InMemory, SQLite). Throws
NotSupportedExceptionon DynamoDB, NCache, Memcached, and FASTER KV.
Remove all cache entries matching any of the given prefixes in a single call. Equivalent to calling InvalidateByPrefixAsync in a loop, but expressed as a single intent:
await cache.InvalidateByPrefixesAsync([
"products:list:",
"products:search:",
"dashboard:stats:"
]);Useful when a single mutation invalidates entries across multiple key namespaces.
Keys passed to ICacheWeaveService are used verbatim — the global KeyVersion and KeySeparator from CacheWeaveOptions are not automatically applied. If you want version-aware keys, include the version manually:
var version = options.Value.KeyVersion ?? string.Empty;
var key = string.IsNullOrEmpty(version)
? "products:list"
: $"products:list:{version}";
var result = await cache.GetOrSetAsync<List<ProductDto>>(key, factory, expiry);Or inject ICacheKeyBuilder to build keys consistently:
public class ProductService(
ICacheWeaveService cache,
ICacheKeyBuilder keyBuilder,
IOptions<CacheWeaveOptions> options)
{
public async Task<List<ProductDto>> GetAllAsync()
{
// Build a key the same way the filter would
var key = keyBuilder.Build("products:list", options.Value, queryParams: null, contextSegment: null);
return await cache.GetOrSetAsync<List<ProductDto>>(key, factory, TimeSpan.FromMinutes(5));
}
}public class MaterialCategoryService(
ICacheWeaveService cache,
AppDbContext db,
IOptions<CacheWeaveOptions> opts)
{
private string Key(string suffix) =>
$"material-categories:{opts.Value.KeyVersion}:{suffix}";
public Task<List<MaterialCategoryDto>> GetAllAsync(CancellationToken ct = default)
=> cache.GetOrSetAsync(
Key("list"),
factory: ct => db.MaterialCategories
.OrderBy(c => c.Name)
.Select(c => new MaterialCategoryDto(c))
.ToListAsync(ct),
expiry: TimeSpan.FromMinutes(10),
ct: ct);
public async Task<MaterialCategoryDto> CreateAsync(
CreateMaterialCategoryCommand cmd,
CancellationToken ct = default)
{
var entity = new MaterialCategory(cmd);
db.MaterialCategories.Add(entity);
await db.SaveChangesAsync(ct);
var dto = new MaterialCategoryDto(entity);
await cache.SetAsync(Key($"detail:{dto.Id}"), dto, TimeSpan.FromMinutes(10), ct);
await cache.InvalidateByPrefixAsync(Key("list"), ct);
return dto;
}
public async Task DeleteAsync(Guid id, CancellationToken ct = default)
{
await db.MaterialCategories.Where(c => c.Id == id).ExecuteDeleteAsync(ct);
await cache.InvalidateAsync(Key($"detail:{id}"), ct);
await cache.InvalidateByPrefixAsync(Key("list"), ct);
}
}