DotNetSmartCache 1.0.0

dotnet add package DotNetSmartCache --version 1.0.0
                    
NuGet\Install-Package DotNetSmartCache -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DotNetSmartCache" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DotNetSmartCache" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DotNetSmartCache" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DotNetSmartCache --version 1.0.0
                    
#r "nuget: DotNetSmartCache, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DotNetSmartCache@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DotNetSmartCache&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DotNetSmartCache&version=1.0.0
                    
Install as a Cake Tool

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

  1. Always use key prefixes to prevent collisions
  2. Enable fallback on error to prevent crashes when cache fails
  3. Invalidate cache when data changes
  4. Monitor cache statistics to optimize performance
  5. Set appropriate expiration times based on data freshness requirements
  6. Use sliding expiration for frequently accessed data
  7. Invalidate related caches when updating related data

API Reference

  • SmartCache<TKey, TValue> - Smart cache with fallback and invalidation
  • SmartCacheOptions<TKey, TValue> - Options for cache configuration
  • GetOrSetAsync() - Get from cache or set using value factory
  • Invalidate() - Invalidate specific cache key
  • InvalidateAll() - Invalidate all cache entries
  • GetStatistics() - Get cache performance statistics
  • CacheStatistics - Cache statistics (hits, misses, hit rate)
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.