I am attempting to put a wrapper around a repository call, so that the items can be cached.
Here is a cut down overview:
I have an interface for listing categories:
public interface ICategoryReadRepository {
Task<IEnumerable<DomainCategory>> ListCategoriesAsync();
}
I have a class implementing that Interface that directly hits the database:
public class CategoryReadRepository : ICategoryReadRepository
{
private readonly Context _context;
public CategoryReadRepository(Context context)
{
_context = context;
}
public async Task<IEnumerable<DomainCategory>> ListCategoriesAsync()
{
// code here that awaits the call to the database and returns the results
}
}
And finally, I have my caching class. This will inspect the cache for a value, and populate it from the database if not found:
public class CachedCategoryReadRepository : ICategoryReadRepository
{
private readonly CategoryReadRepository _repository;
private readonly IMemoryCache _cache;
private MemoryCacheEntryOptions cacheOptions;
private readonly int CacheExpiryInSeconds = 200;
public CachedCategoryReadRepository(CategoryReadRepository repository, IMemoryCache cache)
{
_repository = repository;
_cache = cache;
cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(relative: TimeSpan.FromSeconds(CacheExpiryInSeconds));
}
public async Task<IEnumerable<DomainCategory>> ListCategoriesAsync()
{
string cacheKey = "Category";
return await _cache.GetOrCreateAsync(cacheKey, entry =>
{
entry.SetOptions(cacheOptions);
return _repository.ListCategoriesAsync(categoryTypeId);
});
}
}
Finally, I have this when registering the services as part of startup:
builder.RegisterRepository<ICategoryReadRepository, CachedCategoryReadRepository>();
// other registrations
builder.Services.AddMemoryCache();
builder.Services.AddScoped<MemoryCache>();
builder.Services.AddScoped<IMemoryCache, MemoryCache>();
builder.Services.AddScoped<CategoryReadRepository>();
I am not sure if all 4 lines are required, as I have been experimenting with different options in order to try and get it to work.
Anyway, my initial request works. If I put a breakpoint on the GetOrCreateAsync call from the second line above, I can see that initially, the MemoryCache is adding the result to the collection after performing the database lookup:
As you can see, there is 1 item in the collection exiting the function
However, if I call the exact same method again, the Memory Cache is empty again:
This is the value of the Memory Cache before the lookup again. I would have expected that the value from the previous lookup should have persisted, but it has not, meaning another database call is performed.
What am I overlooking here?
Many thanks and best regards
Change this:
builder.Services.AddScoped<MemoryCache>();
builder.Services.AddScoped<IMemoryCache, MemoryCache>();
builder.Services.AddScoped<CategoryReadRepository>();
To this:
builder.Services.AddSingleton<MemoryCache>();
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();
builder.Services.AddSingleton<CategoryReadRepository>();
You are creating new instance per-call with AddScoped, and you see new instance each time.