Какие существуют времена жизни объектов в DI контейнере: transient, scoped, singleton
Что проверяют
Знаете ли вы три времени жизни и понимаете ли, чем опасно их смешивание.
Как отвечать
- Transient: новый экземпляр на каждое обращение к контейнеру. Подходит для лёгких объектов без состояния;
- Scoped: один экземпляр на область. В ASP.NET Core область по умолчанию создаётся на каждый HTTP-запрос, поэтому «на запрос» и «scoped» обычно совпадают. Сюда относится
DbContext: он не потокобезопасен и обязан жить ровно один запрос; - Singleton: один экземпляр на всё приложение. Подходит для кэшей, конфигурации, клиентов без состояния. Обязан быть потокобезопасным, потому что к нему одновременно обращаются все запросы.
Главная ловушка, ради которой и задают вопрос, это захваченная зависимость: синглтон, зависящий от scoped-объекта. Первый разрешённый экземпляр останется в синглтоне навсегда, и через него будет использоваться DbContext, переживший свой запрос. Отсюда падения и гонки. Контейнер это ловит: при ValidateScopes (включено в среде разработки по умолчанию) он бросает исключение при попытке разрешить scoped из корневого контейнера.
Как правильно поступить, когда синглтону всё же нужен scoped-объект: внедрить IServiceScopeFactory, создать область на время операции и брать зависимость из неё. Это же относится к фоновым службам: BackgroundService регистрируется синглтоном, и работа с базой внутри него делается только через явно созданную область.
Вторая ловушка: transient с IDisposable. Контейнер отвечает за освобождение того, что создал, и объект, разрешённый из корневого контейнера, будет удерживаться до завершения приложения. Это выглядит как утечка. Внутри области такой объект освобождается вместе с ней.
Про HttpClient стоит упомянуть отдельно: его не регистрируют вручную синглтоном, а используют IHttpClientFactory, который решает и проблему исчерпания сокетов, и проблему устаревшего DNS.
Где спотыкаются
Внедряют DbContext в синглтон.
Регистрируют кэш как transient и удивляются, что он пуст.
Не знают про IServiceScopeFactory и работу с областью в фоновой службе.
Считают, что контейнер сам освободит любой созданный объект вовремя.