Search code examples
c#memorycache

C# MemoryCache not persisting values


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:

enter image description here

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:

enter image description here

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


Solution

  • 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.