This project demonstrates 5 different caching patterns in C# with working, executable examples.
- Cache-Aside (Lazy Loading) - Most common pattern
- Write-Through - Keep cache and DB in sync
- Write-Back (Write-Behind) - Fast writes with async DB updates
- Write-Around - Avoid cache pollution
- Read-Through - Clean cache abstraction
Caches/
├── Models/
│ ├── Product.cs # Domain model
│ └── User.cs # Domain model
├── Database/
│ └── SimulatedDatabase.cs # In-memory DB with latency simulation
├── Patterns/
│ ├── CacheAsidePattern.cs # Cache-Aside implementation
│ ├── WriteThroughPattern.cs # Write-Through implementation
│ ├── WriteBackPattern.cs # Write-Back implementation
│ ├── WriteAroundPattern.cs # Write-Around implementation
│ └── ReadThroughPattern.cs # Read-Through implementation
└── Program.cs # Interactive demo app
cd /Users/knanda/source/mini-projects/Caches
dotnet run --project CachesYou'll see an interactive menu:
📋 Select a cache pattern to demo:
1. Cache-Aside (Lazy Loading)
2. Write-Through
3. Write-Back (Write-Behind)
4. Write-Around
5. Read-Through
6. Compare All Patterns
0. Exit
How it works:
- Application checks cache first
- On miss: load from DB → cache it → return
- On write: update DB → invalidate cache
Code Example:
public async Task<Product?> GetProductAsync(int id)
{
var cacheKey = $"product:{id}";
// Check cache
if (_cache.TryGetValue(cacheKey, out Product? cached))
return cached;
// Load from DB
var product = await _db.GetProductAsync(id);
// Cache it
_cache.Set(cacheKey, product, _cacheExpiration);
return product;
}When to use:
- ✅ Read-heavy workloads
- ✅ Most common pattern (used in your Degreed API!)
- ✅ Resilient to cache failures
How it works:
- Write to cache AND database synchronously
- Both must succeed for operation to complete
- Cache is always fresh
Code Example:
public async Task UpdateProductAsync(Product product)
{
var cacheKey = $"product:{product.Id}";
// Update cache first
_cache.Set(cacheKey, product, _cacheExpiration);
// Then update database
await _db.UpdateProductAsync(product);
}When to use:
- ✅ Strong consistency required
- ✅ Read-after-write scenarios
- ❌ Higher write latency (dual write)
How it works:
- Write to cache immediately
- Queue database write for background processing
- Very fast writes, eventual consistency
Code Example:
public async Task UpdateProductAsync(Product product)
{
// Update cache (fast)
_cache.Set($"product:{product.Id}", product);
// Queue DB write (async)
await _writeQueue.Writer.WriteAsync(product);
// Returns immediately!
}When to use:
- ✅ Write-heavy workloads
- ✅ Analytics, logging systems
- ❌ Risk of data loss if cache crashes
How it works:
- Writes bypass cache, go straight to DB
- Cache is invalidated on write
- Next read will cache miss and reload
Code Example:
public async Task UpdateProductAsync(Product product)
{
// Write to DB only
await _db.UpdateProductAsync(product);
// Invalidate cache
_cache.Remove($"product:{product.Id}");
}When to use:
- ✅ Infrequently read data
- ✅ Bulk operations
- ✅ Avoid cache pollution
How it works:
- Cache library handles loading automatically
- Application doesn't need cache-miss logic
- Clean abstraction over cache-aside
Code Example:
public async Task<Product?> GetProductAsync(int id)
{
return await _cache.GetOrCreateAsync($"product:{id}", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = _cacheExpiration;
return await _db.GetProductAsync(id);
});
}When to use:
- ✅ Cleaner code
- ✅ Library-managed caching
- Similar to Cache-Aside but more abstracted
| Pattern | Read Latency | Write Latency | Consistency | Complexity |
|---|---|---|---|---|
| Cache-Aside | Medium (lazy) | Low (DB only) | Eventual | Low |
| Write-Through | Low (cached) | High (dual) | Strong | Low |
| Write-Back | Low (cached) | Very Low (async) | Eventual | High |
| Write-Around | High (miss) | Low (DB only) | Eventual | Low |
| Read-Through | Medium (auto) | Low (DB only) | Eventual | Low |
- No External Dependencies - Uses in-memory simulated database
- Latency Simulation - 100ms delays show caching benefits
- DB Operation Tracking - See exactly when DB is hit
- Interactive Examples - Run each pattern independently
- Side-by-Side Comparison - Compare all patterns at once
- Cache-Aside is the most common pattern (used in most .NET APIs)
- Write-Through for strong consistency
- Write-Back for maximum write performance (with risk)
- Write-Around to avoid cache pollution
- Read-Through for cleaner abstractions
- .NET 8.0
- Microsoft.Extensions.Caching.Memory - In-memory cache
- System.Threading.Channels - Background queue for Write-Back
- Simulated Database - No SQL Server needed!
Want to extend this project? Try:
- Add distributed caching with Redis
- Implement cache stampede protection
- Add refresh-ahead pattern
- Implement TTL refresh on access
- Add metrics and monitoring
No database setup required! Everything runs in-memory with simulated latency.
Run dotnet run --project Caches and explore! 🚀