Ответ
Scoped — это одно из трёх основных времён жизни сервиса в системе внедрения зависимостей (DI) ASP.NET Core. Scoped-сервис создаётся один раз в рамках определённой области (scope) и используется повторно всеми компонентами внутри этой области.
Ключевое применение: В веб-приложении каждый HTTP-запрос автоматически создаёт новую область. Поэтому Scoped-сервис живёт ровно столько, сколько длится обработка одного запроса.
Регистрация:
// В Program.cs или Startup.ConfigureServices
services.AddScoped<IMyService, MyService>();
services.AddScoped<ApplicationDbContext>(); // Типичный пример для EF Core
Почему это важно? Практический пример с Entity Framework Core:
Контекст БД (DbContext) по умолчанию регистрируется как Scoped. Это гарантирует, что:
- В рамках одного запроса все репозитории и сервисы работают с одним экземпляром
DbContext. - Отслеживание изменений (change tracking) работает корректно.
- В конце запроса контекст автоматически освобождается.
- Разные запросы не мешают друг другу, так как используют разные экземпляры.
Важные правила и ограничения:
-
Нельзя внедрять Scoped-сервис в Singleton-сервис. Это приведёт к тому, что Scoped-сервис будет захвачен Singleton'ом и станет вечно живущим, что вызовет проблемы (например, утечку памяти в
DbContext).// НЕПРАВИЛЬНО: services.AddSingleton<IBadService>(provider => new BadService(provider.GetRequiredService<IMyScopedService>())); // Scoped захвачен Singleton'ом -
Для явного создания области (например, в фоновой задаче или консольном приложении) используйте
IServiceScopeFactory.public class BackgroundWorker { private readonly IServiceScopeFactory _scopeFactory; public BackgroundWorker(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public void DoWork() { using (var scope = _scopeFactory.CreateScope()) { var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); // Работаем с scopedService в изолированной области } // Здесь scopedService будет уничтожен } }
Другие времена жизни для сравнения:
- Transient: Создаётся новый экземпляр при каждом запросе сервиса. Подходит для лёгких, stateless-сервисов.
- Singleton: Создаётся один экземпляр на всё время работы приложения. Подходит для stateless-сервисов, кэшей, конфигураций.
Ответ 18+ 🔞
Давай разжую про Scoped, чтобы понятно стало, как этот зверь живёт. Представь себе, что твоё веб-приложение — это общепит, а DI-контейнер — суровый завскладом, который решает, кому и когда выдавать ингредиенты.
Scoped — это как порция салата оливье, которую готовят один раз на конкретный заказ (HTTP-запрос) и ставят на стол. Все, кто сидят за этим столом (контроллеры, сервисы, репозитории внутри одного запроса), едят из одной и той же миски. Как только гости ушли — миску в помойку, на следующий заказ будет новая.
Как его впихнуть в систему:
// Тыкаешь это где-то в Program.cs
services.AddScoped<IMyService, MyService>();
// Или вот классика — контекст БД:
services.AddScoped<ApplicationDbContext>(); // EF Core, ёпта
А нахуя это нужно? Ну смотри на примере Entity Framework Core.
Контекст БД (DbContext) по умолчанию — Scoped. Это гениально, потому что:
- Весь твой запрос, от контроллера до последнего репозитория, долбит базу через один и тот же экземпляр
DbContext. - Отслеживание сущностей (change tracking) не ебёт мозг, всё согласовано.
- Как запрос отработал — контекст прибивается, память чиста.
- Параллельные запросы не дерутся за один контекст, у каждого своя изолированная песочница. Красота!
Но есть подводные ебли, которые надо знать:
-
НЕЛЬЗЯ тыкать Scoped-сервис в Singleton. Это пиздец. Singleton живёт вечно, он схватит твой Scoped-сервис (например, тот же
DbContext) и будет таскать его с собой, как привязанную покойницу. Контекст начнёт копить отслеживаемые сущности, утечка памяти, гонки данных — всё, пизда приложению.// ТАК НЕ ДЕЛАТЬ, А ТО ВЫЛЬЕТСЯ В БОЛЬ: services.AddSingleton<IBadService>(provider => new BadService(provider.GetRequiredService<IMyScopedService>())); // Scoped теперь в плену у Singleton -
Если тебе вдруг надо создать область вручную (допустим, в фоновой хуйне или консольном приложении), бери в руки
IServiceScopeFactory.public class BackgroundWorker { private readonly IServiceScopeFactory _scopeFactory; public BackgroundWorker(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public void DoWork() { using (var scope = _scopeFactory.CreateScope()) { // Достаём сервис из новой, свежеиспечённой области var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>(); // Делаем что надо... } // Всё, scope кончился — сервисы внутри него отправились на свалку истории. } }
Чтобы было совсем ясно, глянь на других его братьев:
- Transient: Как одноразовые стаканчики. Каждый раз, когда кто-то просит "дай сервис" — новый стаканчик. Лёгкие, без состояния.
- Singleton: Как единственный самовар на весь ресторан. Создали один раз при старте и он стоит, греется, все из него пьют. Только для тех, кто не портится и не хранит состояние запроса.