Какая разница между services.AddTransient и services.AddScope в ASP.NET Core?csharp-74

Основные концепции жизненного цикла сервисов

В ASP.NET Core Dependency Injection существует три основных времени жизни сервисов:

  1. Transient - создается новый экземпляр для каждого запроса к сервису
  2. Scoped - один экземпляр на область (обычно HTTP-запрос)
  3. Singleton - один экземпляр на все приложение

services.AddTransient

Характеристики:

  • Новый экземпляр создается каждый раз, когда сервис запрашивается
  • Не зависит от контекста запроса
  • Подходит для легковесных, stateless сервисов

Пример использования:

services.AddTransient<IEmailService, EmailService>();

Когда использовать:

  • Сервисы без состояния
  • Быстрые операции без ресурсоемкой инициализации
  • Когда каждый потребитель должен получить свой экземпляр

services.AddScoped

Характеристики:

  • Один экземпляр создается на область (scope), обычно на HTTP-запрос
  • Один и тот же экземпляр возвращается в рамках одного запроса
  • Подходит для сервисов, которые должны сохранять состояние в течение запроса

Пример использования:

services.AddScoped<IUserRepository, UserRepository>();

Когда использовать:

  • Сервисы, работающие в контексте запроса
  • Entity Framework DbContext (стандартный вариант регистрации)
  • Сервисы, требующие сохранения состояния в рамках запроса

Практическое сравнение

Пример с Transient:

// Регистрация
services.AddTransient<IOperation, Operation>();

// В контроллере
public class TestController : Controller
{
    private readonly IOperation _op1;
    private readonly IOperation _op2;

    public TestController(IOperation op1, IOperation op2)
    {
        // op1 и op2 - РАЗНЫЕ экземпляры
        _op1 = op1;
        _op2 = op2;
    }
}

Пример с Scoped:

// Регистрация
services.AddScoped<IOperation, Operation>();

// В контроллере
public class TestController : Controller
{
    private readonly IOperation _op1;
    private readonly IOperation _op2;

    public TestController(IOperation op1, IOperation op2)
    {
        // op1 и op2 - ОДИН И ТОТ ЖЕ экземпляр в рамках запроса
        _op1 = op1;
        _op2 = op2;
    }
}

Особенности работы в разных контекстах

В рамках HTTP-запроса

  • Scoped: Один экземпляр на весь запрос
  • Transient: Новый экземпляр при каждом обращении

Вне HTTP-запроса

  • Scoped: Требует явного создания scope
  • Transient: Работает как обычно

Пример создания scope вручную:

using (var scope = serviceProvider.CreateScope())
{
    var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
    // Работа с scopedService
}

Производительность и память

  • Transient: Может создавать больше нагрузки на GC при интенсивном использовании
  • Scoped: Оптимален для сервисов, используемых многократно в рамках запроса

Типичные ошибки

  1. Использование Scoped сервиса в Singleton:

    services.AddSingleton<IBackgroundService>(provider =>
        new BackgroundService(provider.GetRequiredService<IScopedService>())); // ОШИБКА!
    
  2. Использование Transient для тяжелых сервисов:

    services.AddTransient<IDatabaseService>(_ =>
        new DatabaseService(heavyConnection)); // Неэффективно
    

Лучшие практики

  1. Для DbContext (EF Core) - всегда Scoped
  2. Для сервисов аутентификации - обычно Scoped
  3. Для легковесных утилитарных сервисов - Transient
  4. Для кешей и общих ресурсов - Singleton

Резюмируем:


Основное различие между AddTransient и AddScoped заключается в времени жизни создаваемых экземпляров сервисов. AddTransient создает новый экземпляр при каждом запросе сервиса, в то время как AddScoped создает один экземпляр на область видимости (обычно HTTP-запрос). Правильный выбор между ними критически важен для корректной работы приложения, управления ресурсами и предотвращения subtle bugs.