Простой инжектор - создайте универсальный декоратор для кэширования EF Core - PullRequest
0 голосов
/ 02 октября 2019

Я пытаюсь реализовать кэширование для EF Core в моем проекте .NET Core, используя Simple Injector в качестве моего DI. Я использую шаблон CQRS, поэтому у меня есть несколько запросов, которые я хотел бы кэшировать (не все).

Я создал общий интерфейс для кэшированного запроса, который принимает тип возврата запроса и аргументы запроса:

public interface ICachedQuery<T, P>
{
    T Execute(P args);

    string CacheStringKey { get; set; }
}

И вот один из моих запросов:

public class GetAssetsForUserQuery : ICachedQuery<Task<List<Asset>>, User>
{
    readonly IDataContext dataContext;
    public string CacheStringKey { get; set; }

    public GetAssetsForUserQuery(IDataContext dataContext)
    {
        CacheStringKey = "GetAssetsForUserQuery";
        this.dataContext = dataContext;
    }

    public async Task<List<Asset>> Execute(User user)
    {
        var allAssets = dataContext.Assets.ToList();
        return allAssets;
    }

}

Мой декоратор не слишком уместен в этом случае, но вот подпись:

public class CachedCachedQueryDecorator<T, P> : ICachedQuery<T, P>

Я регистрирую свой запрос и декоратор в Startup.cs примерно так:

Container.RegisterDecorator(typeof(ICachedQuery<,>), typeof(CachedCachedQueryDecorator<,>));

Container.Register<GetAssetsForUserQuery>();

И я впрыскиваю свой GetAssetsForUserQuery так:

readonly GetAssetsForUserQuery getAssetsForUserQuery;

        public GetTagsForUserQuery(GetAssetsForUserQuery getAssetsForUserQuery)
        {
            this.getAssetsForUserQuery = getAssetsForUserQuery;
        }

Но мой декоратор никогда не попал! Теперь, если я зарегистрирую свой запрос к интерфейсу ICachedQuery в Startup.cs, например, так:

Container.Register(typeof(ICachedQuery<,>), typeof(GetAssetsForUserQuery));

И я добавлю ICachedQuery вместо GetAssetsForUserQuery, тогда мой декоратор ударит. Но ICachedQuery является универсальным, поэтому я не могу разрешить его для одного конкретного запроса.

Я знаю, что делаю что-то в корне неправильно, какая-нибудь помощь?

1 Ответ

2 голосов
/ 02 октября 2019

Но мой декоратор никогда не попал!

Это верно. Чтобы понять, почему это так, лучше всего визуализировать граф объектов, который вы хотите построить:

new GetTagsForUserQuery(
    new CachedCachedQueryDecorator<Task<List<Asset>>, User>(
        new GetAssetsForUserQuery()))

СОВЕТ ПРО: Для многих проблем, связанных с DI, очень полезно построить требуемый граф объектов на простом C #, как показано в предыдущем фрагменте кода. Это дает вам четкую мысленную модель. Это не только полезная модель для вас, это полезный способ сообщить другим, чего вы пытаетесь достичь. Это часто гораздо труднее понять, когда просто показывают регистрации DI.

Однако, если вы попробуете это, этот код не скомпилируется. Он не скомпилируется, потому что GetTagsForUserQuery требует GetAssetsForUserQuery в своем конструкторе, но CachedCachedQueryDecorator<Task<List<Asset>>, User> - это не GetTagsForUserQuery - они оба ICachedQuery<Task<List<Asset>>, User>, но это не то, что GetTagsForUserQuery требует.

Из-за этого технически невозможно обернуть GetAssetsForUserQuery CachedCachedQueryDecorator и вставить этот декоратор в GetTagsForUserQuery. И то же самое верно, когда вы будете разрешать GetAssetsForUserQuery непосредственно из Simple Injector следующим образом:

GetAssetsForUserQuery query = container.GetInstance<GetAssetsForUserQuery>();

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

Что бы сработало, однако, запрашивает тип по его абстракции :

ICachedQuery<Task<List<Asset>>, User> query =
    container.GetInstance<ICachedQuery<Task<List<Asset>>, User>>();

В этом случае вы запрашиваете ICachedQuery<Task<List<Asset>>, User>, и контейнер может вернуть вам любой тип, если он реализует ICachedQuery<Task<List<Asset>>, User>.

То же самое относится к вашему GetTagsForUserQuery. Только когда вы позволяете ему зависеть от ICachedQuery<,>, можно украсить его. Поэтому решение состоит в том, чтобы зарегистрировать GetAssetsForUserQuery по его абстракции:

Container.RegisterDecorator(
    typeof(ICachedQuery<,>),
    typeof(CachedCachedQueryDecorator<,>));

Container.Register<ICachedQuery<Task<List<Asset>>, User>, GetAssetsForUserQuery>();

Но обратите внимание на следующие вещи:

  • Независимо от того, выполняются ли ваши запросы (я обычно называю их«обработчики», но то, что в названии) кешируются или нет - это деталь реализации. Вам не нужно определять другую абстракцию для кешируемых запросов, и потребители не должны знать об этом.
  • Вместо предоставления отдельного CacheStringKey, вместо этого используйте P args в качестве кешаключ. Это можно сделать, например, сериализацией args в объект JSON. Это делает кеширование более прозрачным. В случае, если объект args очень сложный, количество записей в кеше будет слишком большим, так что обычно вам нужно только кешировать результаты очень простых arg запросов.
  • Независимо от того, кэшировать или нет,скорее это детали реализации, которые либо должны быть включены в Composition Root , либо в часть реализации запроса (обработчика). Обычно я делаю это, помечая эту реализацию атрибутом, но интерфейс также может работать. Затем вы можете применить декоратор условно .
  • Запретить предоставление полностью разорванных сущностей как в качестве входных, так и выходных данных для вашего запроса (обработчики). Вместо этого используйте отдельные ориентированные на данные POCO (например, DTO). Что значит отправить User в качестве ввода? Однако гораздо понятнее, когда вы отправляете объект GetAllUserAssets. Это GetAllUserAssets может содержать только свойство UserId. Это позволяет очень легко превратить этот объект в кэшируемую запись. То же самое касается выходных объектов. Сущности очень трудно надежно кэшировать. Это намного проще с POCO или DTO. Их можно сериализовать с гораздо меньшими усилиями и рисками.

Я писал о архитектурах в стиле CQRS в прошлом. Смотрите, например, эту статью . Эта статья объясняет некоторые из пунктов, изложенных выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...