Каковы практические сценарии использования методов IServiceCollection.AddTransient, IServiceCollection.AddSingleton и IServiceCollectionAddScoped?

Прочитав этот пост, я могу понять различия между AddTransient, AddScoped и AddSingleton, однако я не вижу практического использования каждого из них.

Мое понимание

AddTransient

Создает новый экземпляр каждый раз, когда клиент запрашивает его.

services.AddTransient<IDataAccess, DataAccess>();

будет возвращать новый объект DataAccess каждый раз, когда клиентский код запрашивает его. Скорее, конструктор.

Использование AddTransient

В случаях, когда нам нужно получить доступ к базе данных, чтобы прочитать и обновить ее, а также уничтожить объект доступа (DataAccess), лучше всего использовать AddTransient - Не уверен насчет безопасности потока.

AddScoped

Создает новый экземпляр для каждого веб-запроса http.

Использование AddScoped

 services.AddScoped<ShoppingCart>(serviceProvider => ShoppingCart.GetShoppingCart(serviceProvider));

это означает, что каждый веб-запрос будет иметь свой собственный экземпляр корзины покупок, что означает, что каждый пользователь/клиент будет иметь свой собственный экземпляр корзины покупок для этого веб-запроса http.

AddSingleton

Создайте один экземпляр для всех веб-запросов http.

Использование AddSingleton

Нашел этот код в примере приложения, но я не понимаю, как это полезно.

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 

Кто-нибудь может привести хороший практический пример, когда использовать AddSingleton и проверить, правильно ли я понимаю AddTransient и AddScoped?

Ответ 1

Ваше понимание всех трех областей правильное.

Переходный будет использоваться, когда компонент не может использоваться совместно. Одним из примеров может быть объект, не поддерживающий потоки данных.

Scoped может использоваться для контекстов базы данных Entity Framework. Основная причина заключается в том, что тогда объекты, полученные из базы данных, будут привязаны к тому же контексту, который видят все компоненты в запросе. Конечно, если вы планируете параллельно выполнять запросы с ним, вы не можете использовать Scoped.

Другим примером объекта Scoped будет какой-то класс RequestContext, который содержит, например, имя пользователя вызывающего абонента. Фильтр middleware/MVC может запросить его и заполнить информацию, а другие компоненты по этой линии могут также запросить его, и он обязательно будет содержать информацию для текущего запроса.

Компоненты

Singleton разделяются всегда, поэтому они лучше всего подходят для потокобезопасных компонентов, которые не обязательно должны привязываться к запросу. Примером может быть IOptions, который дает доступ к настройкам конфигурации. Класс оболочки HttpClient, который использует SendAsync в одном статическом экземпляре HttpClient, также будет полностью потокобезопасным и хорошим кандидатом на синглтон.

Обратите внимание, что если у вас есть компонент Singleton, который зависит от компонента Scoped, его зависимость будет удаляться перед ним. Таким образом, компонент не может зависеть от другого компонента, который имеет меньшую область действия, чем сам.

Ответ 2

Я видел "просто использовать AddTransient<T>() ", но я не согласен.

Подумайте о распределении памяти

Я ненавижу выделять вещи, когда мне это не нужно, поэтому, если я знаю, что создаю что-то поточно-ориентированное или у меня есть явная документация, что использование экземпляра синглтона является ожидаемым использованием, тогда я создаю синглтон.

AddSingleton()

Здесь экземпляр ApplicationInsights TelemetryClient как единичный. Их документация говорит, что это работает.

telemetryClient = new TelemetryClient(TelemetryConfiguration.Active);
services.AddSingleton<TelemetryClient>(telemetryClient);

В этом проекте я также использую хранилище таблиц Azure и обнаружил, что создание CloudTableClient в качестве синглтона работает просто отлично. Мне не нужно создавать экземпляры для каждого запроса.

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Configuration.GetValue<string>("storageAccountConnectionString"));
CloudTableClient someTableClient = storageAccount.CreateCloudTableClient();
services.AddSingleton<CloudTableClient>(someTableClient);

В некотором смысле это эквивалент статического свойства класса только для чтения, например:

public static readonly CloudTableClient cloudTableClient = new CloudTableClient(...);

... во всем приложении есть только один его экземпляр, но с помощью services.AddSingleton<T>() мы получаем прямой доступ к нему с помощью Dependency Injection.

AddScoped()

Примером AddScoped<T>() для меня является то, что я хочу встроить JavaScript, необходимый для получения Application Insights, в веб-страницу, но я использую Content-Security-Policy, поэтому мне нужно добавить одноразовый номер в любой элемент страница JavaScript. У меня есть немного кода, который помогает мне сделать это.

services.AddScoped<ApplicationInsightsJsHelper>();

AddTransient()

Я еще не нашел необходимости использовать AddTransient<T>() для чего-либо еще. Может случиться так, что я не думаю о вещах, которые создаю каждый раз, когда они мне нужны как "услуги"... это просто переменные, которые я создаю. В некотором смысле AddTransient<T>() является скрытым использованием шаблона Factory... вместо вызова статической функции MyServiceFactory.Create() вас есть Dependency Injection (эффективно), который делает то же самое для вас.