Generated Code¶
The CacheStore generator produces standard .NET interfaces and implementations that work with plain async/await — no effects required. For the optional Eff<RT, T> composition layer, see Effects Composition.
Generated Cache Interface¶
For each [Cached] entity, the generator produces a typed cache interface:
public interface IProductCache
{
Task<Product?> GetAsync(ProductId id, CancellationToken ct = default);
Task SetAsync(ProductId id, Product value, TimeSpan? expiration = null, CancellationToken ct = default);
Task RemoveAsync(ProductId id, CancellationToken ct = default);
}
The interface uses the entity's [TypedId] property as the key parameter, so cache operations are fully typed — no raw string keys needed.
ICacheStore-Backed Implementation¶
A default implementation is generated for every entity that delegates to ICacheStore with automatic key management:
public sealed class ProductCache(ICacheStore cache) : IProductCache
{
private const string KeyPrefix = "product";
public Task<Product?> GetAsync(ProductId id, CancellationToken ct = default)
=> cache.GetAsync<Product>($"{KeyPrefix}:{id}", ct);
public Task SetAsync(ProductId id, Product value, TimeSpan? expiration = null, CancellationToken ct = default)
=> cache.SetAsync($"{KeyPrefix}:{id}", value, expiration, ct);
public Task RemoveAsync(ProductId id, CancellationToken ct = default)
=> cache.RemoveAsync($"{KeyPrefix}:{id}", ct);
}
This means all generated entity caches share the same underlying ICacheStore — configure Redis once, all caches benefit.
DI Registration¶
The generator creates a registration extension method:
// Generated
public static IServiceCollection AddAppCache(this IServiceCollection services)
{
services.AddDefaultCacheStore(); // ICacheStore → DistributedCacheStore → IDistributedCache
services.TryAddSingleton<IProductCache, ProductCache>();
services.TryAddSingleton<IOrderCache, OrderCache>();
return services;
}
AddDefaultCacheStore() registers ICacheStore backed by IDistributedCache (in-memory fallback if none registered). TryAddSingleton means implementations registered first take priority.
Injecting and Using Directly¶
Use the generated interface with standard constructor injection:
public class ProductService(IProductCache cache, IProductStore store)
{
public async Task<Product?> GetAsync(ProductId id, CancellationToken ct)
{
var cached = await cache.GetAsync(id, ct);
if (cached is not null) return cached;
var product = await store.GetByIdAsync(id, ct);
if (product is not null)
await cache.SetAsync(id, product, TimeSpan.FromMinutes(10), ct);
return product;
}
public async Task InvalidateAsync(ProductId id, CancellationToken ct)
=> await cache.RemoveAsync(id, ct);
}
Swapping the Cache Backend¶
Register your preferred IDistributedCache before calling AddAppCache():
// Redis via Aspire
builder.AddRedisDistributedCache("cache");
// Or register directly
services.AddStackExchangeRedisCache(options =>
options.Configuration = "localhost:6379");
// AddAppCache() won't override your IDistributedCache — all entity caches use Redis
services.AddAppCache();
You can also override ICacheStore entirely for custom backends: