Как сделать слой кэширования в ASP.NET/EF Core?

121
16 июня 2019, 11:40

Всем привет.

Архитектура приложения построена таким образом:


Запросы от EF не будет удовлетворять требуемой производительности, да и каждый раз будет излишняя нагрузка на БД.. Пока далеко не зашел, нужно исключить пробел в построении архитектуры кэширования, чем и хочу обернуть уровень доменных сервисов. На данный момент запросы идут в соответствии с доменной логикой, со всей требуемой вложенностью. И только придя к этапу кэширования понимаю, что это может быть не правильным, по крайней мере я не могу найти решение моей проблемы. Т.е. на репозиторий будет оверрайд CachedRepository, и взаимодействие в нем с DistributedCache/MemCache(тут уже не важно).

Интерфейс репозитория:

public interface IRepository<T> where T : class, IEntity
{
    IQueryable<T> GetMany();
    IQueryable<T> GetMany(Expression<Func<T, bool>> predicate);
    T GetSingle(Expression<Func<T, bool>> expression);
    T GetSingle(long id);
    Task<T> GetSingleAsync(Expression<Func<T, bool>> expression);
    bool Any(Expression<Func<T, bool>> expression);
    void Add(T entity);
    Task AddAsync(T entity);
    void AddRange(IEnumerable<T> entitys);
    void Update(T entity);
    void UpdateRange(IEnumerable<T> entities);
    void Delete(T entity);
    void DeleteRange(IEnumerable<T> entities);
    void AddOrUpdateRange(IEnumerable<T> entities);
}

Вот пример метода доменного сервиса:

public class QuestionDomainService : IQuestionDomainService
{
    private readonly IDbContext _dbContext;
    private readonly IRepository<Question> _questionRepository;
    private readonly FileSettings _fileSettings;
    public QuestionDomainService(
        IDbContext dbContext,
        IRepository<Question> questionRepository,
        IOptions<FileSettings> fileSettings)
    {
        _dbContext = dbContext;
        _questionRepository = questionRepository;
        _fileSettings = fileSettings.Value;
    }
    public async Task<QuestionReadDto> ReadAsync(ISpecification<Question> spec)
    {
        return new QuestionReadQuery(_questionRepository, _fileSettings).Query(spec);
    }
    /*...*/
}

Сам Query:

public class QuestionReadQuery
{
    private readonly IRepository<Question> _questionRepository;
    private readonly FileSettings _fileSettings;
    public QuestionReadQuery(IRepository<Question> questionRepository, FileSettings fileSettings)
    {
        _questionRepository = questionRepository;
        _fileSettings = fileSettings;
    }
    public async Task<QuestionReadDto> Query(ISpecification<Question> spec)
    {
        return await _questionRepository.GetMany()
            .Include(o => o.SubscriptionToQuestions)
            .Include(o => o.Author)
                .ThenInclude(o => o.Avatar)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.Category)
                .ThenInclude(o => o.FileInfo)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.Category)
                .ThenInclude(o => o.Parent)
                .ThenInclude(o => o.FileInfo)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.CommentToQuestions)
                .ThenInclude(o => o.Comment)
                .ThenInclude(o => o.Author)
                .ThenInclude(o => o.Avatar)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.CommentToQuestions)
                .ThenInclude(o => o.Comment)
                .ThenInclude(o => o.Reciever)
            .Include(o => o.Answers)
                .ThenInclude(o => o.Author)
                .ThenInclude(o => o.Avatar)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.Answers)
                .ThenInclude(o => o.LikeToAnswers)
            .Include(o => o.Answers)
                .ThenInclude(o => o.CommentToAnswers)
                .ThenInclude(o => o.Comment)
                .ThenInclude(o => o.Author)
                .ThenInclude(o => o.Avatar)
                .ThenInclude(o => o.FileInfoToImageSizes)
                .ThenInclude(o => o.ImageSize)
            .Include(o => o.Answers)
                .ThenInclude(o => o.CommentToAnswers)
                .ThenInclude(o => o.Comment)
                .ThenInclude(o => o.Reciever)
            .Where(spec.Value)
            .Select(SelectorExp())
            .FirstOrDefaultAsync();
    }
    /*...*/
}

Основная проблема в том, что кэширование большой вложенности влечет за собою опасность не корректности данных на вложенных уровнях, т.е. поменялась аватарка пользователя и актуальность данных рухнула.. Либо смотреть обьекты с типом(User) у которого изменился аватар, и во всех кэшах менять ссылку, но оболочка EF Core не позволяет реализовать такого великолепного решения(велосипеда).

Везде описаны архитектуры кэширования с простыми запросами одной вложенности, таким образом я предполагаю, что реализация слоя кэширования возможна при обращении к репозиторию одной вложенности. А при надобности вложенных сущностей, обращаться к репозиториям вложенных сущностей?

В таком случае на первый запрос по широкой доменному требованию(когда много зависимостей), и при этом ничего не закэшировано(после релиза, например). Хорошо, смотрим в сторону лоадеров. Но при огромном количестве записей в базу этот релиз может затянутся на долго, да и база не выдержит.. Как бороться с этим?

Еще неизвестно как быть со спецификациями, т.к. неизвестно условие запроса, и нельзя обратиться к MemCache например.. Их нужно убирать?

Да и в целом, поделитесь мыслями, а еще лучше конкретной реализацией :)

READ ALSO
Проверка попадания точки в GraphicsPath

Проверка попадания точки в GraphicsPath

Был создан полигон, состоящий из трёх точек и одна независимая точка:

117
Почему иногда не удается найти html-узел (HTMLAgilityPack)?

Почему иногда не удается найти html-узел (HTMLAgilityPack)?

Этот код выполняется в нескольких потоках и обычно все нормальноНо иногда SelectSingleNode возвращает null

134
Как заблокировать только горизонтальную автопрокрутку?

Как заблокировать только горизонтальную автопрокрутку?

Как заблокировать только горизонтальную автопрокрутку и оставить автопрокрутку вертикальную?

126
LINQ to SQL использование списка как фильтр

LINQ to SQL использование списка как фильтр

Помогите разобратьсяЯ только начал изучать Sharp и LINQ по этому каждый шаг дается с трудом, вот и сейчас столкнулся с проблемой, решение которой...

112