Nextended.Cache
Caching utilities and extensions for simplified caching operations in .NET applications.
Overview
Nextended.Cache provides caching extensions for IMemoryCache and System.Runtime.Caching.MemoryCache, along with a CacheProvider class for expression-based caching with automatic cache invalidation.
Installation
dotnet add package Nextended.Cache
Key Features
1. CacheProvider with Expression-Based Caching
The CacheProvider class provides intelligent caching based on method expressions with automatic key generation and conditional cache clearing.
using Nextended.Cache;
using Microsoft.Extensions.Caching.Memory;
// Create cache provider
var cacheProvider = new CacheProvider();
// Or with custom options
var cacheProvider = new CacheProvider(
cache: myMemoryCache,
cacheEntryOptions: new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30))
);
// Cache method execution results automatically
public class UserService
{
private readonly CacheProvider _cache;
public User GetUser(int userId)
{
// ExecuteWithCache generates cache key from method expression
return this.ExecuteWithCache(_cache, () => LoadUserFromDb(userId));
}
private User LoadUserFromDb(int userId)
{
// Expensive database operation
return database.Users.Find(userId);
}
}
2. AddOrGetExisting for ObjectCache
Thread-safe lazy initialization for System.Runtime.Caching.MemoryCache.
using Nextended.Cache.Extensions;
using System.Runtime.Caching;
var cache = MemoryCache.Default;
// Thread-safe get or create
var user = cache.AddOrGetExisting(
key: "user:123",
valueFactory: () => LoadUserFromDatabase(123),
absoluteExpiration: DateTimeOffset.Now.AddMinutes(10)
);
// The valueFactory only executes if the item isn't in cache
// Multiple concurrent calls with the same key won't result in multiple executions
3. ExecuteWithCache Extensions for IMemoryCache
Execute and cache method results with automatic key generation from expressions.
using Nextended.Cache.Extensions;
using Microsoft.Extensions.Caching.Memory;
public class ProductService
{
private readonly IMemoryCache _cache;
public Product GetProduct(int productId)
{
// Cache key is automatically generated from the expression
var info = this.ExecuteWithCache(
_cache,
() => LoadProductFromDb(productId),
new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(15))
);
// info.Result contains the product
// info.IsNewEntry tells you if it was cached or newly loaded
// info.Key contains the generated cache key
return info.Result;
}
private Product LoadProductFromDb(int productId)
{
return database.Products.Find(productId);
}
}
4. Conditional Cache Clearing
Clear cache based on conditions with automatic monitoring.
var cacheProvider = new CacheProvider();
// Clear cache automatically when a condition is met
cacheProvider.ClearWhen(cache =>
{
// Clear if more than 1 hour has passed since last write
return (DateTime.Now - cache.LastWriteTime).TotalHours > 1;
});
// Clear manually
cacheProvider.Clear();
// Check cache size
int itemCount = cacheProvider.Count();
// Subscribe to clear event
cacheProvider.Cleared += (sender, args) =>
{
Console.WriteLine("Cache was cleared!");
};
Usage Examples
Expression-Based Caching
using Nextended.Cache;
using Nextended.Cache.Extensions;
public class DataService
{
private readonly CacheProvider _cacheProvider;
private readonly IMemoryCache _memoryCache;
public DataService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_cacheProvider = new CacheProvider(memoryCache);
}
public List<Product> GetProducts(string category)
{
// Cache key is automatically generated from method signature and parameters
return this.ExecuteWithCache(_cacheProvider, () =>
LoadProductsFromDb(category));
}
public User GetUserProfile(int userId)
{
// Using IMemoryCache directly
var cacheInfo = this.ExecuteWithCache(
_memoryCache,
() => LoadUserProfile(userId),
new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(20))
);
if (cacheInfo.IsNewEntry)
{
Console.WriteLine($"Loaded user {userId} from database");
}
return cacheInfo.Result;
}
private List<Product> LoadProductsFromDb(string category)
{
return database.Products.Where(p => p.Category == category).ToList();
}
private User LoadUserProfile(int userId)
{
return database.Users
.Include(u => u.Profile)
.FirstOrDefault(u => u.Id == userId);
}
}
Thread-Safe Caching with ObjectCache
using System.Runtime.Caching;
using Nextended.Cache.Extensions;
public class ConfigurationService
{
private readonly ObjectCache _cache = MemoryCache.Default;
public AppSettings GetSettings()
{
// Multiple threads calling this will only execute LoadSettings once
return _cache.AddOrGetExisting(
"app:settings",
() => LoadSettings(),
DateTimeOffset.Now.AddHours(1)
);
}
public string GetConnectionString(string name)
{
return _cache.AddOrGetExisting(
$"connection:{name}",
() => LoadConnectionString(name),
ObjectCache.InfiniteAbsoluteExpiration // Never expires
);
}
private AppSettings LoadSettings()
{
// Expensive operation - read from file, database, etc.
return Configuration.LoadFromFile("appsettings.json");
}
private string LoadConnectionString(string name)
{
return Configuration.GetConnectionString(name);
}
}
Automatic Cache Invalidation
using Nextended.Cache;
public class CachedDataService
{
private readonly CacheProvider _cache;
public CachedDataService()
{
_cache = new CacheProvider();
// Configure automatic cache clearing
ConfigureCacheInvalidation();
}
private void ConfigureCacheInvalidation()
{
// Clear cache after 2 hours of inactivity
_cache.ClearWhen(cache =>
(DateTime.Now - cache.LastWriteTime).TotalHours > 2);
// Clear cache if it grows too large
_cache.ClearWhen(cache => cache.Count() > 1000);
// Set custom check interval
_cache.ClearCheckInterval = TimeSpan.FromMinutes(5);
// Log when cache is cleared
_cache.Cleared += (sender, args) =>
Logger.Info("Cache was automatically cleared");
}
public Product GetProduct(int id)
{
return this.ExecuteWithCache(_cache, () => LoadProduct(id));
}
private Product LoadProduct(int id)
{
return database.Products.Find(id);
}
}
Custom Cache Entry Options
using Microsoft.Extensions.Caching.Memory;
using Nextended.Cache;
public class AdvancedCachingService
{
private readonly CacheProvider _cache;
public AdvancedCachingService()
{
// Configure default cache options
var options = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(TimeSpan.FromHours(1))
.SetSlidingExpiration(TimeSpan.FromMinutes(20))
.RegisterPostEvictionCallback((key, value, reason, state) =>
{
Console.WriteLine($"Cache key {key} was evicted: {reason}");
});
_cache = new CacheProvider(
cache: null, // Will create default MemoryCache
cacheEntryOptions: options
);
}
public Data GetData(string key)
{
return this.ExecuteWithCache(_cache, () => LoadData(key));
}
private Data LoadData(string key)
{
return dataSource.Get(key);
}
}
Best Practices
1. Use Expression-Based Caching for Method Results
// Good: Automatic key generation based on method and parameters
return this.ExecuteWithCache(_cache, () => ExpensiveOperation(param1, param2));
// The cache key is automatically generated from the expression
// Different parameters result in different cache keys
2. Monitor Cache Performance
public class MonitoredCacheService
{
private readonly CacheProvider _cache;
public MonitoredCacheService()
{
_cache = new CacheProvider();
// Subscribe to events
_cache.Cleared += OnCacheCleared;
// Set up periodic monitoring
_cache.ClearWhen(cache =>
{
var count = cache.Count();
if (count > 0)
Logger.Debug($"Cache size: {count} items");
return false; // Don't clear
});
}
private void OnCacheCleared(object sender, EventArgs e)
{
Logger.Info("Cache was cleared");
}
}
3. Use Appropriate Expiration Strategies
var cacheProvider = new CacheProvider(
cacheEntryOptions: new MemoryCacheEntryOptions()
// Use sliding expiration for frequently accessed data
.SetSlidingExpiration(TimeSpan.FromMinutes(30))
// Use absolute expiration for time-sensitive data
.SetAbsoluteExpiration(TimeSpan.FromHours(2))
);
4. Handle Cache Invalidation Properly
public class UserService
{
private readonly CacheProvider _cache;
public void UpdateUser(User user)
{
database.Users.Update(user);
database.SaveChanges();
// Clear cache to ensure fresh data on next request
_cache.Clear();
}
}
API Reference
CacheProvider
- Constructor:
CacheProvider(IMemoryCache cache = null, MemoryCacheEntryOptions cacheEntryOptions = null) - ExecuteWithCache:
T ExecuteWithCache<TInstance, T>(TInstance owner, Expression<Func<TInstance, T>> expression) - Clear:
void Clear()- Clears all cached items - Count:
int Count()- Returns the number of items in cache - ClearWhen:
CacheProvider ClearWhen(Func<CacheProvider, bool> predicate)- Registers a condition for automatic cache clearing - Properties:
MemoryCacheEntryOptions CacheEntryOptions- Default cache entry optionsTimeSpan ClearCheckInterval- Interval for checking clear conditions (default: 10 minutes)DateTime LastWriteTime- Time of last cache write
- Events:
EventHandler<EventArgs> Cleared- Fired when cache is cleared
CacheExtensions
- AddOrGetExisting:
T AddOrGetExisting<T>(this ObjectCache cache, string key, Func<T> valueFactory, DateTimeOffset absoluteExpiration, string regionName = null) - ExecuteWithCache:
CacheExecutionInfo<TResult> ExecuteWithCache<TParam, TResult>(this TParam param, IMemoryCache cache, Expression<Func<TParam, TResult>> expression, MemoryCacheEntryOptions entryOptions = null)
Configuration
ASP.NET Core Integration
// Program.cs
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CacheProvider>();
// Or with custom configuration
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 1024;
options.CompactionPercentage = 0.25;
});
builder.Services.AddSingleton(sp =>
{
var memoryCache = sp.GetRequiredService<IMemoryCache>();
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
return new CacheProvider(memoryCache, cacheOptions);
});
Supported Frameworks
- .NET Standard 2.0
- .NET Standard 2.1
- .NET 8.0
- .NET 9.0
Dependencies
Nextended.Core- Core utilities and extensionsMicrosoft.Extensions.Caching.Abstractions- Caching abstractionsMicrosoft.Extensions.Caching.Memory- In-memory cachingSystem.Runtime.Caching- Runtime caching support
Performance Considerations
- Automatic Key Generation: Cache keys are generated from method expressions, including parameters
- Thread Safety:
AddOrGetExistinguses lazy initialization for thread-safe caching - Memory Usage: Monitor cache size using
Count()method - Automatic Invalidation: Use
ClearWhenfor condition-based cache clearing - Check Interval: Adjust
ClearCheckIntervalbased on your needs
Related Projects
- Nextended.Core - Foundation library with core extensions