Persistence Caching

Caching persistence data in RCommon

Occasionally there is the need to cache data that is coming from your persistence layer so that you can avoid hitting your data store repeatedly for data that is commonly used. RCommon provides a proxy for all repository implementations available in RCommon which allows you to use the repository pattern in conjunction with caching services.

Configuration

Note the usage of "ef.AddInMemoryPersistenceCaching" in the configuration below. This will configure the caching repository interfaces for you to use.

var host = Host.CreateDefaultBuilder(args)
                .ConfigureServices(services =>
                {
                    // Configure RCommon
                    services.AddRCommon()
                        .WithPersistence<EFCorePerisistenceBuilder>(ef => // Repository/ORM configuration. We could easily swap out to Linq2Db without impact to domain service up through the stack
                        {
                            // Add all the DbContexts here
                            ef.AddDbContext<TestDbContext>("TestDbContext", ef =>
                            {
                                ef.UseSqlServer(config.GetConnectionString("TestDbContext"));
                            });
                            ef.AddInMemoryPersistenceCaching(); // This gives us access to the caching repository interfaces/implementations
                        })
                        // You still need to configure this as well!
                        .WithMemoryCaching<InMemoryCachingBuilder>(cache =>
                        {
                            cache.Configure(x =>
                            {
                                x.ExpirationScanFrequency = TimeSpan.FromMinutes(1);
                            });
                        });
                }).Build();

It is still important that you configure caching in addition to adding persistence caching since that is what implements the caching abstractions.

Usage

public class TestApplicationService : ITestApplicationService
{
    private readonly ICachingGraphRepository<Customer> _customerRepository;

    public TestApplicationService(ICachingGraphRepository<Customer> customerRepository)
    {
        _customerRepository = customerRepository;
        _customerRepository.DataStoreName = "TestDbContext";
    }

    public async Task<ICollection<Customer>> GetCustomers(object cacheKey)
    {
        return await _customerRepository.FindAsync(cacheKey, x => x.LastName == "Potter");
    }
}

Behind the Scenes

This is what is happening behind the scenes in the caching repository proxy

public class CachingGraphRepository<TEntity> : ICachingGraphRepository<TEntity>
    where TEntity : class, IBusinessEntity
{
    private readonly IGraphRepository<TEntity> _repository;
    private readonly ICacheService _cacheService;

    public CachingGraphRepository(IGraphRepository<TEntity> repository, ICommonFactory<PersistenceCachingStrategy, ICacheService> cacheFactory)
    {
        _repository = repository;
        _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default);
    }

    public async Task<IPaginatedList<TEntity>> FindAsync(object cacheKey, Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity,
        object>> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default)
    {
        var data = await _cacheService.GetOrCreateAsync(cacheKey, 
            async () => await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token));
        return await data;
    }
    
    public async Task<IPaginatedList<TEntity>> FindAsync(object cacheKey, IPagedSpecification<TEntity> specification, CancellationToken token = default)
    {
        var data = await _cacheService.GetOrCreateAsync(cacheKey,
            async () => await _repository.FindAsync(specification, token));
        return await data;
    }
    
    public async Task<ICollection<TEntity>> FindAsync(object cacheKey, ISpecification<TEntity> specification, CancellationToken token = default)
    {
        var data = await _cacheService.GetOrCreateAsync(cacheKey,
             async () => await _repository.FindAsync(specification, token));
        return await data;
    }
    
    public async Task<ICollection<TEntity>> FindAsync(object cacheKey, Expression<Func<TEntity, bool>> expression, CancellationToken token = default)
    {
        var data = await _cacheService.GetOrCreateAsync(cacheKey,
            async () => await _repository.FindAsync(expression, token));
        return await data;
    }

Last updated