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?
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.
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.
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.
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>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With