0

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

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

you are a legend, thank you very much. I had to leave CategoryReadRepository as Scoped for the App to run, otherwise, I got a runtime error, but changing the other two to AddSingleton worked perfectly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.