0

I have Query Repository

public interface IQueryRepository<T> where T : BaseEntity
{
    Task<IReadOnlyList<TRes>> ListAsync<TRes>(ISpecification<T> spec,
        CancellationToken cancellationToken = default);
    Task<T> FirstOrDefaultAsync(ISpecification<T> spec, CancellationToken cancellationToken = default);
    Task<TRes> FirstOrDefaultAsync<TRes>(ISpecification<T> spec, CancellationToken cancellationToken = default) where TRes : class;
}

I have created a proxy between my client and repository.

public class CachedReadonlyRepositoryProxy<T> : IQueryRepository<T> where T : BaseEntity
{
    private readonly EfReadonlyRepository<T> _repository;
    private readonly ICacheService _cacheService;
    private int? _cachingDuration;
    private readonly ISettingsService _settingsService;
    private readonly bool _isCacheable;

    public CachedReadonlyRepositoryProxy(EfReadonlyRepository<T> repository, ICacheService cacheService, ISettingsService settingsService)
    {
        _repository = repository;
        _cacheService = cacheService;
        _settingsService = settingsService;

        _isCacheable = typeof(T).IsDefined(typeof(CacheableAttribute), false);
    }

    public async Task<T> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        if (!_isCacheable)
            return await _repository.GetByIdAsync(id, cancellationToken);

        var key = "";
        var cachedRecord = _cacheService.Get<T>(key);
        if (cachedRecord != null)
            return cachedRecord;

        var result = await _repository.GetByIdAsync(id, cancellationToken);
        _cacheService.Store(key, result, await GetDuration());
        return result;
    }

    public async Task<T> FirstOrDefaultAsync(ISpecification<T> spec, CancellationToken cancellationToken = default)
    {
        if (!_isCacheable)
            return await _repository.FirstOrDefaultAsync(spec, cancellationToken);

        var key = "";
        var cachedRecord = _cacheService.Get<T>(key);
        if (cachedRecord != null)
            return cachedRecord;

        var result = await _repository.FirstOrDefaultAsync(spec, cancellationToken);
        _cacheService.Store(key, result, await GetDuration());
        return result;
    }
}

The problem is that I can't create a unique key for every query. I don't know how to distinguish specifications (Ardalis Specification library). Web API ASP.NET Core 3.1

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
voryba
  • 63
  • 1
  • 5

1 Answers1

0

ISpecification<T> has CacheKey property. As per docs to enable caching for your specifications you need call EnableCache when defining specification, for example:

public class CustomerByNameWithStoresSpec : Specification<Customer>, ISingleResultSpecification
{
    public CustomerByNameWithStoresSpec(string name)
    {
        Query.Where(x => x.Name == name)
            .Include(x => x.Stores)
            .EnableCache(nameof(CustomerByNameWithStoresSpec), name);
    }
}

Also note:

Implementing caching will also require infrastructure such as a CachedRepository, an example of which is given in the sample on GitHub. The EnableCache method is used to inform the cache implementation that caching should be used, and to configure the CacheKey based on the arguments supplied.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I have a lot of specification classes in my project. In addition, many specs are built as fluent-api (new DefaultSpecification().ById().Color(Red)...). So i need other way to handle this – voryba Mar 24 '23 at 09:27
  • @voryba What is `DefaultSpecification`? Also note, that you need the values passed to the specs, so you need do use either this or do something similar. – Guru Stron Mar 24 '23 at 09:37
  • @voryba also note that fluent id approach can be relatively manageable (you combine previous cache key with current one). Problem will be different ordering, but 1) it is better then nothing 2) you can workaround by using custom spec builder. – Guru Stron Mar 24 '23 at 09:45