Можно ли передать scoped зависимость в singleton
Что проверяют
Понимаете ли вы, что фактическое время жизни зависимости определяет не её регистрация, а тот, кто её получил. Вопрос ловит классическую ошибку с захватом контекста базы в долгоживущий объект, и по ответу видно, читали ли вы сообщения контейнера или гасили их наугад.
Как отвечать
Прямо в конструктор синглтона scoped зависимость передать нельзя. Встроенный контейнер это проверяет: при включённой проверке областей попытка даёт InvalidOperationException с текстом про потребление scoped сервиса из singleton. В среде разработки проверка включена, а сборка провайдера с ValidateOnBuild роняет приложение на старте, а не на первом запросе.
Опаснее случай, когда проверку отключили: зависимость захватывается навсегда. Контекст базы данных становится общим для всего приложения, живёт до перезапуска, копит отслеживаемые сущности и ломается на параллельных обращениях, потому что потокобезопасным он не задумывался.
Правильный путь это создавать область самому:
public sealed class Worker(IServiceScopeFactory scopeFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
}
}Для контекста базы есть и более узкий вариант: зарегистрировать фабрику через AddDbContextFactory и брать контекст на каждую единицу работы.
Где спотыкаются
Внедряют корневой IServiceProvider и считают вопрос закрытым. Разрешение из корня отдаёт экземпляр, живущий всё приложение, то есть ровно тот захват, от которого уходили. Нужна именно новая область.
Переводят зависимость в синглтон, чтобы контейнер замолчал. Для контекста базы это меняет понятную ошибку конфигурации на гонки в рантайме, которые воспроизводятся только под нагрузкой.
Забывают, что фоновая служба это синглтон. Класс, унаследованный от BackgroundService, регистрируется на всё приложение, и репозиторий в его конструкторе тянет за собой ту же самую проблему.