-
Notifications
You must be signed in to change notification settings - Fork 159
API documentation (v 2.x)
See QuickStart docs
The best way to use LazyCache is through the GetOrAdd method. It uses generics to save you any type casting hassle and a Func to ensure you only call you cacheable delegate once.
// Create the cache
IAppCache cache = new CachingService();
// Get all products from the cache, or get from db and cache results
var products = cache.GetOrAdd("productService-getProducts", () => dbContext.Products.ToList());
Each cache entry must have a unique key, where the key is unique to the underlying cache instance, which by default is the shared MemoryCache.Default.
Constructing a unique key is left to the developer, but a good best practice is to cache the results of a chosen methods call and adopt convention of className-methodName-argument1Value-argument2Value
etc for the method you are adding caching to. Following this naming convention makes it easy to examine keys at run-time and establish the source and guarantees uniqueness.
For example
public class ProductService() {
...
public Product GetProductById(int id){
var key = string.Format("ProductService-GetProductById-{0}", id); //or use string interpolation
return cache.GetOrAdd(key, () => dbContext.Products.Get(id));
}
...
}
var products = cache.GetOrAdd("all-products", () => dbContext.Products.ToList(), DateTimeOffset.Now.AddMinutes(5));
var products = cache.GetOrAdd("all-products", () => dbContext.Products.ToList(), TimeSpan.FromMinutes(5));
If the thing you are caching is fetched asynchronously, i.e. it returns a Task<Something>
you should use GetOrAddAsync()
.
If the thing you are caching is fetched synchronously, i.e. it does not return as task, you should use GetOrAdd()
.
Use GetOrAddAsync to cache tasks. In this example we demonstrate using lazy cache to cache an async Entity Framework query inside a webapi controller:
[HttpGet]
[Route("api/products")]
public async Task<Product> Get(int id)
{
Func<Task<Product>> cacheableAsyncFunc = () => dbContext.Products.GetAsync(id);
var cachedProducts = await cache.GetOrAddAsync($"ProductsController-Get-{id}", cacheableAsyncFunc);
return cachedProducts;
// Or just do it all in one line if you prefer
// return await cache.GetOrAddAsync($"ProductsController-Get-{id}", () => dbContext.Products.GetAsync(id));
}
Most of the time all you need is GetOrAdd but sometimes you might need to force an item into the cache
// Get the cache (or use a DI container)
IAppCache cache = new CachingService();
// Get all products from db
var products = dbContext.Products.ToList();
// Add them to the cache so we can retrieve them up to 20 mins later
cache.Add("all-products", products);
// Add products to the cache for at most one minute
cache.Add("all-products", products, DateTimeOffset.Now.AddMinutes(1));
// Add products to the cache and keep them there as long as they
// have been accessed in the last 5 minutes
cache.Add("all-products", products, new TimeSpan(0, 5, 0));
cache.GetOrAdd("some-key", entry => {
var thingWithAnExpiryDate = GetTheThingToCache();
DateTimeOffset expiryDate = thingWithAnExpiryDate.Expires;
// can set expiry date using a DateTimeOffset or a TimeSpan from now.
entry.SetAbsoluteExpiration(expiryDate);
return thingWithAnExpiryDate;
});
Get notified when something is removed, set cache entry priorities and more by using a MemoryCacheEntryOptions
// Add products to the cache with an unremovable custom cache priority
// add a custom eviction callback
var options = new MemoryCacheEntryOptions(){
Priority = CacheItemPriority.NeverRemove
};
options.RegisterPostEvictionCallback((key, value, reason, state) =>
{
log.Write("Products removed from cache")
});
cache.Add("all-products", products, options);
If you need the last version of your cached data, you can access it by as the second parameter of the callback delegate:
var options = new MemoryCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) // about to expire!
}.RegisterPostEvictionCallback(
(key, value, reason, state) => Debug.Write("this value has just been removed: " + value.ToString())
);
cache.Add("all-products", products, options);
If you have already cached something you can retrieve it, without the need for any type checking or casting, using Get
. If its not cached null will be returned.
// Get the cache
IAppCache cache = new CachingService();
// Get product from the cache based on a key
var product = cache.Get<Product>("product-with-id-1");
If it is cached it will be removed, if not no error will be thrown
// Remove the products from the cache
cache.Remove("all-products");
// Change the default cache duration from 20 minutes to 3 minutes
var cache = new CachingService() { DefaultCachePolicy.DefaultCacheDurationSeconds = 60 * 3 };
Generally this is a code smell - you should remove unwanted items by key. But if you really need to this can be achieved by disposing the implementation of ICacheProvider
which is typically the MemoryCacheProvider
and then creating a new instance of CachingService
with a new provider instance.
Method 1 - Dispose
// Say I already have a cache that I want to dispose:
IAppCache cache = new CachingService();
// now I dispose the provider to clear the cache:
cache.CacheProvider.Dispose()
// Need to create a new provider to replace the disposed one
var provider = new MemoryCacheProvider(
new MemoryCache(
new MemoryCacheOptions()));
// create a new cache instance with the new provider
cache = new CachingService(provider);
Method 2 - Cancellation Tokens (could also be used to dispose a range of items)
// Say I already have a cache:
IAppCache cache = new CachingService();
// I need a cancellation token to link every single cache item to the same cancellation event
// this instance must be shared everywhere you add stuff to the cache - a singleton
var sharedExpiryTokenSource = new CancellationTokenSource(); // IDisposable!
// add first item to the cache and link to the global expiry token
var expireToken1 = new CancellationChangeToken(sharedExpiryTokenSource.Token);
var options1 = new MemoryCacheEntryOptions()
.AddExpirationToken(expireToken)
var product1 = cache.GetOrAdd($"Products-1", () => dbContext.Products.GetAsync(1), options1);
// add second item to the cache and link to the same expiry token
var options2 = new MemoryCacheEntryOptions()
.AddExpirationToken(new CancellationChangeToken(sharedExpiryTokenSource.Token))
var product2 = cache.GetOrAdd($"Products-2", () => dbContext.Products.GetAsync(2), options2);
// And now later on I can remove both products from the cache by cancelling on the shared token
sharedExpiryTokenSource.Cancel();
There are 2 options to configure how LazyCache evicts items from the cache:
- ExpirationMode.LazyExpiration (the default) which removes expired cache items when they are next accessed by key, if they have expired. This is Lazy and uses the least resources.
- ExpirationMode.ImmediateExpiration which uses a timer to remove items from the cache as soon as they expire (and so is more resource intensive)
To use immediate expiration:
var result= await cache.GetOrAddAsync(
"someKey",
() => GetStuff(),
DateTimeOffset.UtcNow.AddSeconds(100),
ExpirationMode.ImmediateExpiration);
And this can also be configured on the LazyCacheEntryOptions
options object when more complex cache item config is required:
var result= await cache.GetOrAddAsync(
"someKey",
entry => GetStuff(),
LazyCacheEntryOptions
.WithImmediateAbsoluteExpiration(TimeSpan.FromSeconds(10))
.RegisterPostEvictionCallback((key, value, reason, state) => Console.WriteLine("cache item immediately evicted on expiry!"))
);
Sometimes you may want to proactively repopulate the cached items on a schedule before they are requested again. This can be achieved using the ImmediateExpiration feature in combination with a callback as shown below:
var key = "someKey";
var refreshInterval = TimeSpan.FromSeconds(30);
// this Local Function is the Func whoose results we are caching
Product[] GetProducts()
{
return ....
}
// this Local Function builds options that will trigger a refresh of the cache entry immediately on expiry
MemoryCacheEntryOptions GetOptions()
{
//ensure the cache item expires exactly on 30s (and not lazily on the next access)
var options = new LazyCacheEntryOptions()
.SetAbsoluteExpiration(refreshInterval, ExpirationMode.ImmediateExpiration);
// as soon as it expires, re-add it to the cache
options.RegisterPostEvictionCallback((keyEvicted, value, reason, state) =>
{
// dont re-add if running out of memory or it was forcibly removed
if (reason == EvictionReason.Expired || reason == EvictionReason.TokenExpired) {
sut.GetOrAdd(key, _ => GetProducts(), GetOptions()); //calls itself to get another set of options!
}
});
return options;
}
// Get products and auto refresh them every 30s
var products sut.GetOrAdd(key, () => GetProducts(), GetOptions());