.NET Core/EF 6 - Область применения для инъекций

В настоящее время я работаю над настройкой приложения .NET Core с использованием EF 6, и у меня возникли проблемы с пониманием правильного использования различных методов регистрации зависимостей. Насколько я понимаю:

  • Переходный: объект создается при необходимости (т.е. каждый раз, когда запрашивается новый экземпляр)
  • Singleton: единственный экземпляр, созданный при запуске приложения, и доступный для всех следующих запросов
  • Область: доступна на время запроса

В частности, в моей ситуации я настроил пару DbContexts (на основе шаблона CQRS) для обработки запросов/команд базы данных, которые я регистрирую как Scoped:

services.AddScoped((_) => new TestCommandContext(Configuration["Data:TestConnection:ConnectionString"]));
services.AddScoped((_) => new TestQueryContext(Configuration["Data:TestConnection:ConnectionString"]));

Это соответствует документации ASP.NET Начало работы с документами ASP.NET 5 и Entity Framework 6:

Контекст должен быть разрешен один раз для каждой области для обеспечения производительности и обеспечения надежной работы Entity Framework

Затем я регистрирую соответствующие классы UOW:

services.AddTransient<ITestCommandUnit, TestCommandUnit>();
services.AddTransient<ITestQueryUnit, TestQueryUnit>();

Я использую Transient здесь на основе этой статьи, которая предполагает, что:

Службы, зарегистрированные в области Transient, создаются всякий раз, когда это необходимо в приложении. Это означает, что новый экземпляр класса (зарегистрированного сервиса) будет создаваться инфраструктурой внедрения зависимостей каждый раз, когда выполняется (метод, в котором создается зависимость).

Основываясь на этом понимании, я использую регистрацию моих классов репозитория и сервисов в Scoped:

services.AddScoped<ITestCommandRepository, TestCommandRepository>();
services.AddScoped<ITestQueryRepository, TestQueryRepository>();

services.AddScoped<ITestCommandService, TestCommandService>();
services.AddScoped<ITestQueryService, TestQueryService>();

Затем вызовите мои соответствующие методы уровня обслуживания в моих контроллерах по мере необходимости:

public class TestController : BaseController
{
    private ITestQueryService testQueryService;

    // Get new object of type TestQueryService via DI
    public TestController(ITestQueryService testQueryService)
    {
        this.testQueryService = testQueryService;
    }

    [HttpGet]
    public IActionResult Edit(int id)
    {
        if (id > 0)
        {
            EditViewModel viewModel = new EditViewModel();
            viewModel.TestObject = testQueryService.GetById(id);
            return View(viewModel);
        }
        else
        {
            return RedirectToAction("Error", new { errorMessage = "No object with the specified Id could be found." });
        }
    }
}

При тестировании эта конфигурация работает, и настройка DbContext (s) по мере того, как Scoped имеет смысл - кажется, не нужно/неэффективно создавать новый объект контекста каждый раз, когда он запрашивал.

Однако выбор между Transient/ Singleton/ Scoped для других объектов - это то место, где я потерялся. Может ли кто-нибудь помочь мне понять наилучшую конфигурацию для этой конкретной реализации шаблонов?

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

Ответ 1

Как правило, мое эмпирическое правило таково:

  1. Scoped - это путь, который сохраняет кеш и ваши волосы, потому что состояние является общим для всего запроса. Нет проблем с параллелизмом (все сервисы с одной областью имеют общий поток). Не создает экземпляры, если класс используется несколько раз в одном запросе. Если я не знаю, как класс должен быть зарегистрирован, я иду на предмет. Также обычно вам нужно что-то несколько раз в одном запросе - вы можете вычислить это один раз и установить значение в поле, чтобы следующие запросы к CreditLimit вашего клиента не попадали в хранилище данных.

  2. Singleton хорош для кэшей (для всего сервера), классов конфигурации, объектов, разработанных с учетом нескольких потоков (несколько запросов). Помните, что синглтон не должен зависеть от не синглтонных объектов. Также следите за вызовом синглетонов в нескольких потоках. Если вам нужен синглтон, чтобы что-то сделать с данными запроса, передайте это как аргумент функции.

  3. Временная регистрация очень редко в моем приложении. Я использую его для классов, которые имеют внутреннее состояние, и они могут использоваться несколько раз, и не должны совместно использовать это состояние. Обычно служебные или каркасные классы.

Пример области применения? SqlConnection - вы не хотите открывать несколько соединений с БД из одного запроса (потому что он обрабатывается пулами соединений). Также сервис, использующий это соединение (сервис делает одно, поэтому нет необходимости в нескольких экземплярах). Контроллер Asp.

Пример синглтона? Самые популярные статьи сегодня. Валидатор почтового индекса (без зависимостей, хотя может быть однозначным).

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

Имейте в виду, что если синглтон имеет временную или ограниченную зависимость, он не будет удален, пока синглтон не будет удален (перезапуск приложения). Следовательно, объекты с областями видимости могут зависеть от отдельных областей, а синглтоны не могут зависеть от областей.

Говоря о CQRS и DbContext - в моем приложении у меня есть один DbContext, общий для команд и запросов. Все регистрируется для всей области действия (команды или запросы не сохраняют состояние после завершения, поэтому их можно использовать повторно. Установка этого параметра также будет работать как переходный процесс). Другим примером является класс, который генерирует уникальный идентификатор для HTML-элементов. Он регистрируется как область действия и увеличивает внутренний счетчик каждый раз, когда запрашивается новый идентификатор. Если класс был временным, он потерял бы свое состояние при вызове из следующих классов.

Знайте, что у некоторых людей есть другие точки зрения. Если вы используете несколько областей действия, возможно, лучше перейти к переходным зависимостям. Мне нравится проходить фабрику, если мне нужно использовать одну зависимость несколько раз, и я стараюсь иметь только одну область действия в моем приложении.

Ответ 2

  • Временные объекты всегда разные; новый экземпляр предоставляется каждый контроллер и каждая служба.
  • Объекты с областью видимости одинаковы в запросе, но разные различные запросы
  • Объекты Singleton для каждого объекта одинаковы и каждый запрос (независимо от того, предоставлен ли экземпляр в ConfigureServices)

В вашем случае служба, которую вы вводите, не зависит от состояния других объектов в рамках одного запроса. Вы используете службу для получения объекта в Db. Будет работать временная или ограниченная служба.

Если в том же запросе вам нужно изменить состояние вашего объекта на основе вычислений в рамках одного и того же запроса, вам нужно будет использовать объекты, которые живут в одном запросе от начала до конца (т.е. с областью действия).