Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for caching data in Asp.Net Core + EF Core?

I have an Asp.Net Core + EF Core REST service. I created a DbContext class for a DB I want to call a SP on. The method pretty much looks like:

public IQueryable<xxx> Getxxxs()
{
    return Set<xxx>().FromSql("pr_Getxxx");
}

This all works, but there isn't any point in calling the SP every single time since the data the SP returns rarely changes. I'd like to make the data stale, say every 24 hours.

Is there a preferred pattern for doing that in Core? I see they have the .AddCaching extension method, but that seems like it would get injected into the controller? So its the controllers job to cache? I assume its thread-safe so I don't need to do any locking or anything like that? Seems like a race condition, if one thread is checking if the item is loaded into the cache, the other may be inserting it, etc?

like image 789
SledgeHammer Avatar asked Nov 14 '16 21:11

SledgeHammer


People also ask

Does EF core cache data?

The most common data to cache in EF Core is transactional data. This is the frequently changing data that is created at runtime (e.g. customer, accounts, activities, etc.) and you cache it only for a short time during which your application reads it multiple times.

What are the different caching techniques available in .NET Core?

ASP.NET Core uses two caching techniques. In-memory caching uses the server memory to store cached data locally, and distributed caching distributes the cache across various servers. We'll explore them both below.


2 Answers

you can use this third party package: https://github.com/VahidN/EFSecondLevelCache.Core

with AspNetCore MW & EfCore Extension (Cacheable) like this:

var posts = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .Cacheable()
                   .ProjectTo<PostDto>(configuration: _mapper.ConfigurationProvider)
                   .ToList();

ProjectTo<>() is AutoMapper Extension.

like image 174
hamid_reza hobab Avatar answered Sep 30 '22 06:09

hamid_reza hobab


Well, you can apply the decorator pattern. It's nothing .NET Core specific, just a common pattern.

public class MyModel
{
    public string SomeValue { get; set; }
}

public interface IMyRepository
{
    IEnumerable<MyModel> GetModel();
}

public class MyRepository : IMyRepository
{
    public IEnumerable<MyModel> GetModel()
    {
        return Set<MyModel>().FromSql("pr_GetMyModel");
    }
}

public class CachedMyRepositoryDecorator : IMyRepository
{
    private readonly IMyRepository repository;
    private readonly IMemoryCache cache;
    private const string MyModelCacheKey = "myModelCacheKey";
    private MemoryCacheEntryOptions cacheOptions;

    // alternatively use IDistributedCache if you use redis and multiple services
    public CachedMyRepositoryDecorator(IMyRepository repository, IMemoryCache cache)
    {
        this.repository = repository;
        this.cache = cache;

        // 1 day caching
        cacheOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(relative: TimeSpan.FromDays(1));
    }

    public IEnumerable<MyModel> GetModel()
    {
        // Check cache
        var value = cache.Get<IEnumerable<MyModel>>("myModelCacheKey");
        if(value==null)
        {
            // Not found, get from DB
            value = repository.GetModel();

            // write it to the cache
            cache.Set("myModelCacheKey", value, cacheOptions);
        }

        return value;
    }
}

Since the ASP.NET Core DI doesn't support interceptors or decorators, your DI registration will become a bit more verbose. Alternatively use a 3rd party IoC container which supports decorator registrations.

services.AddScoped<MyRepository>();
services.AddScoped<IMyRepository, CachedMyRepositoryDecorator>(
    provider => new CachedMyRepositoryDecorator(
        provider.GetService<MyRepository>(),
        provider.GetService<IMemoryCache>()
    ));

This has the advantage that you have a clear separation of concerns and can easily disable the caching by changing the DI configuration to

services.AddScoped<IMyRepository,MyRepository>();
like image 41
Tseng Avatar answered Sep 30 '22 04:09

Tseng