CacheGraph.Attributes
1.0.0
See the version list below for details.
dotnet add package CacheGraph.Attributes --version 1.0.0
NuGet\Install-Package CacheGraph.Attributes -Version 1.0.0
<PackageReference Include="CacheGraph.Attributes" Version="1.0.0" />
<PackageVersion Include="CacheGraph.Attributes" Version="1.0.0" />
<PackageReference Include="CacheGraph.Attributes" />
paket add CacheGraph.Attributes --version 1.0.0
#r "nuget: CacheGraph.Attributes, 1.0.0"
#:package CacheGraph.Attributes@1.0.0
#addin nuget:?package=CacheGraph.Attributes&version=1.0.0
#tool nuget:?package=CacheGraph.Attributes&version=1.0.0
CacheGraph π§ π
Cache-aside with dependency graph and cascade invalidation for .NET
CacheGraph combines the cache-aside pattern with a dependency graph that maps cache keys to business entities. When an entity changes, all cache entries that depend on it are automatically invalidated in cascade β no manual cache key management needed.
Unlike FusionCache, EasyCaching, or Microsoft HybridCache (which only support flat tag-based invalidation), CacheGraph models relationships as a traversable bidirectional graph, enabling true cascade invalidation across entity hierarchies.
π¦ Installation
dotnet add package CacheGraph.Core
For Microsoft.Extensions integration (DI, Logging, IMemoryCache, IDistributedCache):
dotnet add package CacheGraph.Extensions
β¨ Features
- Cache-aside with
GetOrSetAsyncβ on-demand loading with factory and stampede protection - Dependency graph β bidirectional relationships between cache keys and entities
- Cascade invalidation β invalidate all entries that depend on an entity, type, collection, predicate, or query
- Stampede protection β distributed lock ensures only one thread executes the factory per key
- 8 interfaces for extensibility (cache, graph, lock, events, telemetry, logging, serialization)
- DI integration β
AddCacheGraph()/AddCacheGraphWithLogging()for Microsoft.Extensions.DependencyInjection - Cache adapters β wrap
IMemoryCacheorIDistributedCacheasICacheProvider - Events β
CacheSetEvent,CacheRemovedEvent,EntityInvalidatedEvent,QueryInvalidatedEvent - Cross-instance backplane β
CacheGraphBackplanereplicates invalidations between instances over the event provider (in-memory or Redis Pub/Sub) without re-publish loops - Declarative caching β
[CacheResult]attribute +CacheProxy(Castle DynamicProxy) caches interface methods automatically and registers dependencies for cascade invalidation - Redis providers (
CacheGraph.Redis) β distributed cache, graph storage, distributed locks, and pub/sub event invalidation with self-message filtering - Metrics β hits, misses, sets, removes, invalidations, operation times, graph stats
- Thread-safe β all in-memory providers use lock-based writes and snapshot reads
- Benchmarks β BenchmarkDotNet suite comparing CacheGraph vs manual cache-aside vs FusionCache
- Multi-target β .NET 8, .NET 9, and .NET 10
- Zero external dependencies (Core) β only the .NET BCL
π Quick Start
Manual setup
using CacheGraph.Core;
using CacheGraph.Core.Implementaciones;
using CacheGraph.Core.Interfaces;
using CacheGraph.Core.Modelos;
var cacheGraph = new CacheGraph(
graphProvider: new MemoryGraphProvider(),
cacheProvider: new MemoryCacheProvider(),
eventProvider: new MemoryEventProvider(),
logger: new ConsoleLogger(),
telemetryProvider: new NoOpTelemetryProvider(),
distributedLock: new MemoryDistributedLock(),
options: new GraphOptions
{
DefaultTtl = TimeSpan.FromMinutes(30),
LockTimeout = TimeSpan.FromSeconds(30),
LockRetryCount = 3,
});
// Cache-aside with entity dependency
var product = await cacheGraph.GetOrSetAsync(
"product:42",
factory: () => _db.GetProductAsync(42),
dependencies: new[] { "entity:Product:42" },
ttl: TimeSpan.FromMinutes(5));
// When the entity changes, invalidate it β product:42 is removed automatically
await cacheGraph.InvalidateEntityAsync<Product>("42");
// Invalidate all Product cache entries (by type)
await cacheGraph.InvalidateEntityTypeAsync<Product>();
// Invalidate a collection of entities
await cacheGraph.InvalidateCollectionAsync<Product>(new[] { "42", "43", "44" });
// Predicate-based invalidation
await cacheGraph.InvalidateByPredicateAsync<Product>(p => p.Discontinued);
// Query-based invalidation
await cacheGraph.InvalidateByQueryAsync<Product>(q => q.Where(p => p.Price < 10));
Dependency Injection setup
// Program.cs
builder.Services.AddCacheGraph(options =>
{
options.DefaultTtl = TimeSpan.FromMinutes(30);
});
// With Microsoft.Extensions.Logging
builder.Services.AddCacheGraphWithLogging(options =>
{
options.DefaultTtl = TimeSpan.FromMinutes(30);
});
// Fluent builder
builder.Services.AddCacheGraph(builder => builder
.UseMemoryCache()
.UseMicrosoftLogging()
.WithDefaultTtl(TimeSpan.FromMinutes(30))
);
Using IMemoryCache or IDistributedCache as the cache provider
// Wrap IMemoryCache as ICacheProvider
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var cacheProvider = new MemoryCacheProviderAdapter(memoryCache);
// Wrap IDistributedCache as ICacheProvider (uses JSON serialization)
var distributedCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
var cacheProvider = new DistributedCacheProviderAdapter(
distributedCache,
new JsonCacheSerializer());
Declarative caching with attributes (CacheGraph.Attributes)
using CacheGraph.Attributes;
public interface IProductService
{
[CacheResult("products:{id}", 300, "entity:Product:{id}")]
Task<Product> GetByIdAsync(int id);
}
// Manual wiring
var proxy = CacheProxy.Create<IProductService>(new ProductService(), cacheGraph);
// Or via DI
services.AddCacheGraph();
services.AddCacheGraphProxied<IProductService, ProductService>();
Keys support {paramName} placeholders; dependencies can be declared as templates
and are also auto-detected from id/ids parameters (entity:{Type}:{value}).
Cache hits skip the target; cascade invalidation works transparently.
Cross-instance backplane (CacheGraph.Backplane)
// Same event provider shared across instances (Redis Pub/Sub with CacheGraph.Redis)
services.AddCacheGraph();
services.AddCacheGraphBackplane();
// At startup β start replicating remote invalidations
var backplane = provider.GetRequiredService<CacheGraphBackplane>();
await backplane.StartAsync();
Invalidation events carry the affected keys, so every instance removes the same entries from its local cache and graph. Replication is silent β no re-publish loops. With Redis, messages from the publishing instance are filtered out automatically.
Benchmarks (CacheGraph.Benchmarks)
dotnet run --project CacheGraph.Benchmarks -c Release -- --filter '*GetOrSetBenchmarks*'
Covers hot/cold GetOrSetAsync throughput, cascade invalidation cost at
10/100/1000 dependents, and a head-to-head comparison against manual cache-aside
and FusionCache.
ποΈ Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ICacheGraph β
β (main faΓ§ade) β
ββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββ€
β ICache β IGraph β IDistributed β IEvent β
β Provider β Provider β Lock β Provider β
ββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββ€
β ITelemetryProvider β ICacheGraphLogger β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Edge direction: cache-key β entity (the cache key depends on the entity).
product:42 βββββββΊ entity:Product:42 βββββββ products:featured
(dep) (entity) (dep)
cart:user:42 βββββββΊ entity:User:42
(dep) (entity)
When entity:Product:42 is invalidated, the graph traverses incoming edges to find product:42 and products:featured, removing both from the cache.
π Interfaces
| Interface | Purpose |
|---|---|
ICacheGraph |
Main faΓ§ade β cache-aside, invalidation, graph queries |
ICacheProvider |
Key-value storage (get, set, remove, exists) |
IGraphProvider |
Dependency graph (nodes, edges, traversal) |
IDistributedLock |
Distributed lock for stampede protection |
IEventProvider |
Publish/subscribe event system |
ITelemetryProvider |
Activity tracking and performance telemetry |
ICacheGraphLogger |
Logging abstraction |
ICacheSerializer |
Serialization for distributed cache adapters |
βοΈ Configuration
var options = new GraphOptions
{
DefaultTtl = TimeSpan.FromHours(1), // Default cache entry TTL
LockTimeout = TimeSpan.FromSeconds(30), // Max wait to acquire lock
LockRetryCount = 3, // Lock acquisition retries
LockRetryDelay = TimeSpan.FromMilliseconds(100),
EnableValidation = true, // Input validation
};
π§ͺ Tests
dotnet test --framework net8.0
286 tests with xUnit + FluentAssertions, covering:
- Unit tests for all providers, models, and the main CacheGraph faΓ§ade
- DI integration tests
- Cache adapter tests (IMemoryCache, IDistributedCache)
- Concurrency stress tests (stampede protection, concurrent invalidation, concurrent edge addition, metrics accuracy under load)
- Backplane tests (cross-instance invalidation replication, no re-publish loops)
- Redis Pub/Sub tests (channel subscription, self-message filtering, unsubscribe)
- Declarative caching tests (key templates, auto-dependency detection, invalidation)
π― Demo
dotnet run --project CacheGraph.Demo
π License
MIT License β see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
-
net10.0
- CacheGraph (>= 1.0.0)
- Castle.Core (>= 5.1.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
-
net8.0
- CacheGraph (>= 1.0.0)
- Castle.Core (>= 5.1.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
-
net9.0
- CacheGraph (>= 1.0.0)
- Castle.Core (>= 5.1.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.