Можно ли создавать свои скопы Scoped в ASP.NET Core и как ими управлять?
Что проверяют
Понимаете ли вы, что скоуп в ASP.NET Core это не синоним HTTP-запроса, а единица жизни, которую создаёт контейнер, и запрос лишь частный случай. Вопрос почти всегда ведёт к фоновым задачам и к DbContext.
Как отвечать
Да, скоуп создаётся вручную: берёте IServiceScopeFactory, зовёте CreateScope и обязательно освобождаете его. Для зависимостей с асинхронным освобождением есть CreateAsyncScope.
await using var scope = scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();Где это действительно нужно: фоновый сервис живёт всё время работы приложения, то есть по сути синглтон, и зависимости с временем жизни Scoped в конструктор ему не внедрить. Значит на каждую итерацию цикла или на каждое сообщение из очереди создаётся свой скоуп. То же самое с миграциями на старте и с обработчиками по таймеру.
Полезно добавить, зачем эта дисциплина нужна: при закрытии скоупа контейнер освобождает всё, что сам в нём создал. DbContext получает свежий набор отслеживаемых сущностей, соединение возвращается в пул, память не растёт от итерации к итерации.
Где спотыкаются
Первое: внедряют Scoped в синглтон напрямую. Это захваченная зависимость, и контейнер ловит её проверкой скоупов при старте: приложение упадёт с понятной ошибкой вместо того, чтобы тихо работать неправильно.
Второе: резолвят из корневого провайдера, потому что так короче. Полученные объекты живут до остановки приложения и освобождаются вместе с ним, а DbContext при этом копит отслеживаемые сущности. Выглядит как утечка памяти, ей и является.
Третье: запускают параллельные задачи в одном скоупе. DbContext не потокобезопасен, и на второй одновременной операции вы получите ошибку про уже начатую операцию на этом экземпляре. На каждую параллельную ветку нужен свой скоуп либо фабрика контекстов.