DotNetSmartCache 1.0.0
dotnet add package DotNetSmartCache --version 1.0.0
NuGet\Install-Package DotNetSmartCache -Version 1.0.0
<PackageReference Include="DotNetSmartCache" Version="1.0.0" />
<PackageVersion Include="DotNetSmartCache" Version="1.0.0" />
<PackageReference Include="DotNetSmartCache" />
paket add DotNetSmartCache --version 1.0.0
#r "nuget: DotNetSmartCache, 1.0.0"
#:package DotNetSmartCache@1.0.0
#addin nuget:?package=DotNetSmartCache&version=1.0.0
#tool nuget:?package=DotNetSmartCache&version=1.0.0
DotNetSmartCache
Smart caching with fallback strategies, cache invalidation, key collision prevention, and cache observability.
Problems Solved
- Cache invalidation bugs: Cache not invalidated when data changes
- Cache key collisions: Different data types collide in cache keys
- Redis down crashes app: Application crashes when cache is unavailable
- No cache fallback strategy: No fallback when cache fails
- Stale cache serving: Old data served from cache
- Cache per-tenant issues: Cache keys not scoped to tenants
- Memory cache leaks: Cache entries never expire, consuming memory
- No cache observability: No metrics or statistics about cache performance
Installation
dotnet add package DotNetSmartCache
Quick Start
1. Smart Cache with Fallback
Problem: Application crashes when cache (Redis) is unavailable.
using DotNetSmartCache;
using Microsoft.Extensions.Caching.Memory;
// ✅ GOOD: Cache with automatic fallback
var cache = new SmartCache<string, User>(
memoryCache,
async key => await userService.GetUserAsync(key), // Value factory
new SmartCacheOptions<string, User>
{
DefaultExpiration = TimeSpan.FromMinutes(5),
FallbackOnError = true, // Fallback to value factory if cache fails
KeyPrefix = "users" // Prevent key collisions
},
logger
);
// Get or set - automatically falls back if cache fails
var user = await cache.GetOrSetAsync("user-123");
2. Cache Invalidation
Problem: Cache not invalidated when data changes, serving stale data.
public class UserService
{
private readonly SmartCache<string, User> _cache;
public async Task<User> GetUserAsync(string userId)
{
// Get from cache or fetch
return await _cache.GetOrSetAsync(userId);
}
public async Task UpdateUserAsync(string userId, User user)
{
await _repository.UpdateAsync(user);
// ✅ GOOD: Invalidate cache after update
_cache.Invalidate(userId);
}
public async Task DeleteUserAsync(string userId)
{
await _repository.DeleteAsync(userId);
// ✅ GOOD: Invalidate cache after delete
_cache.Invalidate(userId);
}
}
3. Cache Statistics
Problem: No visibility into cache performance (hit rate, misses, etc.).
// ✅ GOOD: Get cache statistics
var stats = cache.GetStatistics();
logger.LogInformation(
"Cache Stats - Total: {Total}, Hits: {Hits}, Misses: {Misses}, Hit Rate: {HitRate:P2}",
stats.TotalEntries,
stats.Hits,
stats.Misses,
stats.HitRate
);
// Use statistics for monitoring
if (stats.HitRate < 0.5)
{
logger.LogWarning("Cache hit rate is low: {HitRate:P2}", stats.HitRate);
}
4. Cache Key Prefixing
Problem: Cache keys from different types collide (e.g., "123" for both User and Order).
// ✅ GOOD: Use key prefix to prevent collisions
var userCache = new SmartCache<string, User>(
memoryCache,
async key => await userService.GetUserAsync(key),
new SmartCacheOptions<string, User>
{
KeyPrefix = "users" // Keys become "users:user-123"
}
);
var orderCache = new SmartCache<string, Order>(
memoryCache,
async key => await orderService.GetOrderAsync(key),
new SmartCacheOptions<string, Order>
{
KeyPrefix = "orders" // Keys become "orders:order-123"
}
);
5. Fallback Value Factory
Problem: Need different fallback strategy when cache fails.
// ✅ GOOD: Custom fallback when cache fails
var cache = new SmartCache<string, User>(
memoryCache,
async key => await userService.GetUserFromDatabaseAsync(key), // Primary
new SmartCacheOptions<string, User>
{
FallbackOnError = true,
FallbackValueFactory = async key =>
{
// Fallback to secondary source if primary fails
return await userService.GetUserFromBackupAsync(key);
}
}
);
Real-World Example
public class ProductService
{
private readonly SmartCache<int, Product> _productCache;
private readonly SmartCache<string, List<Product>> _categoryCache;
public ProductService(
IMemoryCache memoryCache,
IProductRepository repository,
ILogger<ProductService> logger)
{
_productCache = new SmartCache<int, Product>(
memoryCache,
async id => await repository.GetByIdAsync(id),
new SmartCacheOptions<int, Product>
{
DefaultExpiration = TimeSpan.FromMinutes(10),
SlidingExpiration = TimeSpan.FromMinutes(5),
FallbackOnError = true,
KeyPrefix = "products"
},
logger
);
_categoryCache = new SmartCache<string, List<Product>>(
memoryCache,
async category => await repository.GetByCategoryAsync(category),
new SmartCacheOptions<string, List<Product>>
{
DefaultExpiration = TimeSpan.FromMinutes(15),
KeyPrefix = "categories"
},
logger
);
}
public async Task<Product> GetProductAsync(int productId)
{
// Automatically caches and falls back if cache fails
return await _productCache.GetOrSetAsync(productId);
}
public async Task<List<Product>> GetProductsByCategoryAsync(string category)
{
return await _categoryCache.GetOrSetAsync(category);
}
public async Task UpdateProductAsync(Product product)
{
await _repository.UpdateAsync(product);
// Invalidate product cache
_productCache.Invalidate(product.Id);
// Invalidate category cache (product category might have changed)
_categoryCache.Invalidate(product.Category);
}
public CacheStatistics GetCacheStatistics()
{
return new CacheStatistics
{
ProductCache = _productCache.GetStatistics(),
CategoryCache = _categoryCache.GetStatistics()
};
}
}
// Usage in controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly ProductService _productService;
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetProductAsync(id);
return Ok(product);
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateProduct(int id, Product product)
{
await _productService.UpdateProductAsync(product);
return NoContent();
}
[HttpGet("stats")]
public ActionResult<CacheStatistics> GetCacheStats()
{
return Ok(_productService.GetCacheStatistics());
}
}
Cache Invalidation Patterns
// Invalidate single key
cache.Invalidate("user-123");
// Invalidate all (use sparingly)
cache.InvalidateAll();
// Invalidate related caches
public async Task UpdateUserAsync(User user)
{
await _repository.UpdateAsync(user);
// Invalidate user cache
_userCache.Invalidate(user.Id);
// Invalidate user's orders cache
_userOrdersCache.Invalidate(user.Id);
// Invalidate search cache (user name might have changed)
_userSearchCache.InvalidateAll();
}
Best Practices
- Always use key prefixes to prevent collisions
- Enable fallback on error to prevent crashes when cache fails
- Invalidate cache when data changes
- Monitor cache statistics to optimize performance
- Set appropriate expiration times based on data freshness requirements
- Use sliding expiration for frequently accessed data
- Invalidate related caches when updating related data
API Reference
SmartCache<TKey, TValue>- Smart cache with fallback and invalidationSmartCacheOptions<TKey, TValue>- Options for cache configurationGetOrSetAsync()- Get from cache or set using value factoryInvalidate()- Invalidate specific cache keyInvalidateAll()- Invalidate all cache entriesGetStatistics()- Get cache performance statisticsCacheStatistics- Cache statistics (hits, misses, hit rate)
| 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 was computed. 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 was computed. 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. |
-
net8.0
- Microsoft.Extensions.Caching.Memory (>= 8.0.0)
- Microsoft.Extensions.Logging (>= 8.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.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 129 | 12/30/2025 |
Initial release. See README for details.