Различия в сервисах AddTransient, AddScoped и AddSingleton

939

Я хочу реализовать внедрение зависимостей (DI) в ASP.NET Core. Поэтому после добавления этого кода в ConfigureServicesметод оба способа работают.

В чем разница между services.AddTransientи service.AddScopedметодов в ASP.NET Ядра?

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}
Эльвин Мамедов
источник
92
@tmg В документах говорится, что «временные сервисы создаются каждый раз, когда их запрашивают». и «Сервисы с ограниченным сроком действия создаются один раз за запрос». что, если мое понимание английского языка слабее, чем я думал, на самом деле означает то же самое.
Нейтрино
70
@ ТМГ, я знаю. Я просто указываю на то, что документы не совсем ясны в этом вопросе, поэтому указывать людей на документы не очень полезно.
Нейтрино
13
@Neutrino, поэтому я задал этот вопрос.
Эльвин Мамедов
5
Поздно к вечеринке, читая комментарии еще позже, но я распечатал эту статью, прочитал ее и набросал то же самое замечание на полях, которое я сейчас вижу @Neutrino, сделанное здесь. Статья была совершенно неопределенной, предлагая этот анализ. К счастью, пример был менее запутанным.
Wellspring
5
Насколько я понимаю: временные сервисы создаются каждый раз, когда их запрашивают . Слово, запрашиваемое здесь, - это повседневное английское значение запроса чего-либо, в данном случае услуги. В то время как слова запроса в один раз запрос в относится к HTTP - запрос. Но я понимаю растерянность.
Мемет Олсен,

Ответы:

1658

TL; DR

Временные объекты всегда разные; новый экземпляр предоставляется каждому контроллеру и каждой службе.

Объекты области видимости одинаковы в запросе, но различны для разных запросов.

Объекты Singleton одинаковы для каждого объекта и каждого запроса.

Для большей ясности этот пример из документации ASP.NET показывает разницу:

Чтобы продемонстрировать разницу между этими параметрами времени жизни и регистрации, рассмотрим простой интерфейс, который представляет одну или несколько задач как операцию с уникальным идентификатором OperationId. В зависимости от того, как мы настроим время жизни для этой службы, контейнер предоставит запрашивающему классу либо одинаковые, либо разные экземпляры службы. Чтобы было ясно, какое время жизни запрашивается, мы создадим один тип на время жизни:

using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }

    public interface IOperationScoped : IOperation
    {
    }

    public interface IOperationSingleton : IOperation
    {
    }

    public interface IOperationSingletonInstance : IOperation
    {
    }
}

Мы реализуем эти интерфейсы, используя один класс, Operationкоторый принимает GUID в своем конструкторе, или использует новый GUID, если ни один не указан:

using System;
using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Classes
{
    public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance
    {
        Guid _guid;
        public Operation() : this(Guid.NewGuid())
        {

        }

        public Operation(Guid guid)
        {
            _guid = guid;
        }

        public Guid OperationId => _guid;
    }
}

Далее, ConfigureServicesкаждый тип добавляется в контейнер в соответствии с его именованным временем жизни:

services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

Обратите внимание, что IOperationSingletonInstanceслужба использует конкретный экземпляр с известным идентификатором Guid.Empty, поэтому будет понятно, когда этот тип используется. Мы также зарегистрировали значение OperationService, зависящее от каждого из других Operationтипов, чтобы в запросе было ясно, получает ли эта служба тот же экземпляр, что и контроллер, или новый экземпляр для каждого типа операции. Все, что делает этот сервис, представляет его зависимости как свойства, чтобы они могли отображаться в представлении.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}

Чтобы продемонстрировать время жизни объекта внутри и между отдельными индивидуальными запросами к приложению, образец включает в себя тип, OperationsControllerкоторый запрашивает каждый IOperationтип, а также тип OperationService. Затем Indexдействие отображает все значения контроллера и службы OperationId.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // ViewBag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;

            // Operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

Теперь к этому действию контроллера подаются два отдельных запроса:

Первый запрос

Второй запрос

Обратите внимание, какое из OperationIdзначений варьируется в запросе и между запросами.

  • Временные объекты всегда разные; новый экземпляр предоставляется каждому контроллеру и каждой службе.

  • Объекты области видимости одинаковы в запросе, но различаются в разных запросах

  • Объекты Singleton одинаковы для каждого объекта и каждого запроса (независимо от того, предоставлен ли экземпляр ConfigureServices)

akazemis
источник
14
Я понял функции каждого из них, но кто-то может объяснить влияние использования одного вместо другого. Какие проблемы это может вызвать, если не используется правильно или выбрать один вместо другого.
Паван непал
2
Допустим, вы создаете объект, связанный с контекстом запроса (например, текущий пользователь) с одноэлементной областью действия, тогда он будет оставаться одним и тем же экземпляром во всех http-запросах, что нежелательно. IOC - это создание экземпляров, поэтому нам нужно указать область действия созданного экземпляра.
akazemis
1
это! Я упомянул ссылку в верхней части темы! Пример кода скопирован / вставлен из документов MS
akazemis
1
Спасибо. да, синглтон будет одинаковым во всем приложении независимо от сеанса / пользователя. очевидно, если ваше приложение использует архитектуру микросервисов и каждый сервис работает в отдельном процессе, синглтон будет одинаковым в каждом процессе
akazemis
1
Можете ли вы привести пример использования addTransient, пожалуйста? потому что я не нашел каких - либо утилиты , чтобы использовать его в то время как его используют слишком много ресурсов
Тераи
319

В внедрении зависимостей .NET есть три основных срока службы:

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

Сервисы с ограниченным сроком службы создаются один раз для каждого запроса в рамках области. Это эквивалентно синглтону в текущей области видимости. Например, в MVC он создает один экземпляр для каждого HTTP-запроса, но использует тот же экземпляр в других вызовах в рамках того же веб-запроса.

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

Здесь вы можете найти и примеры, чтобы увидеть разницу:

ASP.NET 5 MVC6 Внедрение зависимостей за 6 шагов (ссылка на веб-архив из-за неработающей ссылки)

Ваша инъекция зависимостей готова ASP.NET: ASP.NET 5

А это ссылка на официальную документацию:

Внедрение зависимостей в ASP.NET Core

akazemis
источник
22
Не могли бы вы объяснить, почему переходный процесс является самым легким? Я думал, что Transient - это самая тяжелая работа, потому что она должна создавать экземпляр каждый раз для каждой инъекции.
Эксперт хочет быть
17
Вы правы. Переходный процесс не самый легкий, я только что сказал, что он подходит для легких сервисов RESTful :)
akazemis
3
Так в каком сценарии мы могли бы использовать scoped и в каком переходном процессе в примере контроллера, например, если мы извлекаем несколько строк из базы данных? Я пытаюсь понять сценарий scoped vs переходного использования в этом случае.
Сэнсэй
4
это действительно зависит от логики, которую вы ожидаете. Например, если это один вызов БД, он фактически не имеет значения, какой вы используете. но если вы вызываете db несколько раз в одном и том же запросе, то вы можете использовать время действия области, поскольку он сохраняет один и тот же объект хранилища в памяти и многократно использует его в одном и том же контексте запроса Http. В то время как временный создает новый объект хранилища несколько раз (и потребляет больше памяти). Если вы объясните свой конкретный сценарий, было бы легко определить, какой из них подходит лучше.
Аказемис
3
Здесь следует отметить один важный момент: синглтон, Scoped и Transient похожи на русских учителей, один внутри другого. Невозможно изменить их порядок при вложении, например. область видимости или синглтон не могут содержаться в переходном процессе, потому что мы бы продлили время жизни родителя, что противоречит сдерживанию!
Д.Л. Нарасимхан
34

Transient, scoped и singleton определяют процесс создания объекта в базовом DI ASP.NET MVC, когда необходимо внедрить несколько объектов одного типа. Если вы новичок в внедрении зависимостей, вы можете посмотреть это видео DI IoC .

Вы можете увидеть код контроллера ниже, в котором я запросил два экземпляра «IDal» в конструкторе. Transient, Scoped и Singleton определяют, будет ли один и тот же экземпляр вставлен в «_dal» и «_dal1» или в другое.

public class CustomerController : Controller
{
    IDal dal = null;

    public CustomerController(IDal _dal,
                              IDal _dal1)
    {
        dal = _dal;
        // DI of MVC core
        // inversion of control
    }
}

Переходный процесс: В переходном режиме новые экземпляры объекта будут внедрены в один запрос и ответ. Ниже приведен снимок, на котором я отображал значения GUID.

Введите описание изображения здесь

Scoped: в Scoped один и тот же экземпляр объекта будет внедрен в один запрос и ответ.

Введите описание изображения здесь

Синглтон: в синглтоне один и тот же объект будет внедрен во все запросы и ответы. В этом случае будет создан один глобальный экземпляр объекта.

Ниже приведена простая диаграмма, которая визуально объясняет вышеизложенное.

MVC DI изображение

Изображение выше было нарисовано командой SBSS, когда я проходил тренинг по ASP.NET MVC в Мумбаи . Большое спасибо команде SBSS за создание вышеуказанного изображения.

Шивпрасад Койрала
источник
9
Это самое сложное объяснение временной службы, которую я когда-либо видел. Transient = Каждый раз, когда эта служба разрешается, является эквивалентом присвоения вашей переменной new TService. Scoped будет кэшировать первую его инициализацию для этой «области» (в большинстве случаев HTTP-запрос). Синглтон будет кешировать только один экземпляр за всю жизнь приложения, просто так. Приведенные выше диаграммы настолько запутаны.
Mardoxx
2
Извините, я подумал, что я сделаю это проще с диаграммами и снимком кода :-) Но я понял вашу точку зрения.
Shivprasad Koirala
30
  • Singleton - это единичный экземпляр на весь срок действия домена приложения.
  • Scoped - это один экземпляр на время запроса с ограничением, что означает HTTP- запрос в ASP.NET.
  • Переходный процесс - это отдельный экземпляр для каждого запроса кода .

Обычно запрос кода должен быть сделан через параметр конструктора, как в

public MyConsumingClass(IDependency dependency)

Я хотел бы указать в ответе @ akazemis, что «сервисы» в контексте DI не подразумевают сервисы RESTful; сервисы являются реализациями зависимостей, которые обеспечивают функциональность.

user1969177
источник
16

AddSingleton ()

AddSingleton () создает один экземпляр службы при первом запросе и повторно использует этот же экземпляр во всех местах, где требуется эта служба.

AddScoped ()

В сервисе с определенной областью, с каждым HTTP-запросом, мы получаем новый экземпляр. Однако в рамках одного и того же HTTP-запроса, если служба требуется в нескольких местах, например, в представлении и в контроллере, один и тот же экземпляр предоставляется для всей области действия этого HTTP-запроса. Но каждый новый HTTP-запрос будет получать новый экземпляр службы.

AddTransient ()

В случае временной службы новый экземпляр предоставляется каждый раз, когда запрашивается экземпляр службы, находится ли он в пределах одного и того же HTTP-запроса или в разных HTTP-запросах.

Ясир Шейх
источник
5

После поиска ответа на этот вопрос я нашел блестящее объяснение с примером, которым я хотел бы поделиться с вами.

Вы можете посмотреть видео, которое демонстрирует различия ЗДЕСЬ

В этом примере у нас есть этот код:

public interface IEmployeeRepository
{
    IEnumerable<Employee> GetAllEmployees();
    Employee Add(Employee employee);
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MockEmployeeRepository : IEmployeeRepository
{
    private List<Employee> _employeeList;

    public MockEmployeeRepository()
    {
        _employeeList = new List<Employee>()
    {
        new Employee() { Id = 1, Name = "Mary" },
        new Employee() { Id = 2, Name = "John" },
        new Employee() { Id = 3, Name = "Sam" },
    };
    }

    public Employee Add(Employee employee)
    {
        employee.Id = _employeeList.Max(e => e.Id) + 1;
        _employeeList.Add(employee);
        return employee;
    }

    public IEnumerable<Employee> GetAllEmployees()
    {
        return _employeeList;
    }
}

HomeController

public class HomeController : Controller
{
    private IEmployeeRepository _employeeRepository;

    public HomeController(IEmployeeRepository employeeRepository)
    {
        _employeeRepository = employeeRepository;
    }

    [HttpGet]
    public ViewResult Create()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Create(Employee employee)
    {
        if (ModelState.IsValid)
        {
            Employee newEmployee = _employeeRepository.Add(employee);
        }

        return View();
    }
}

Создать вид

@model Employee
@inject IEmployeeRepository empRepository

<form asp-controller="home" asp-action="create" method="post">
    <div>
        <label asp-for="Name"></label>
        <div>
            <input asp-for="Name">
        </div>
    </div>

    <div>
        <button type="submit">Create</button>
    </div>

    <div>
        Total Employees Count = @empRepository.GetAllEmployees().Count().ToString()
    </div>
</form>

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IEmployeeRepository, MockEmployeeRepository>();
}

Скопируйте и вставьте этот код и нажмите кнопку «Создать» в представлении и переключайтесь между ними AddSingleton, AddScopedи AddTransientкаждый раз вы будете получать другой результат, который может помочь вам понять это объяснение:

AddSingleton () - Как видно из названия, метод AddSingleton () создает службу Singleton. Сервис Singleton создается при первом запросе. Этот же экземпляр затем используется всеми последующими запросами. Таким образом, в общем случае служба Singleton создается только один раз для каждого приложения, и этот единственный экземпляр используется в течение всего срока службы приложения.

AddTransient () - этот метод создает временный сервис. Новый экземпляр временной службы создается каждый раз, когда она запрашивается.

AddScoped () - этот метод создает сервис Scoped. Новый экземпляр службы Scoped создается один раз для каждого запроса в области. Например, в веб-приложении оно создает 1 экземпляр для каждого http-запроса, но использует тот же экземпляр в других вызовах в рамках того же веб-запроса.

Оффир Пеер
источник
2

Какой использовать

преходящий

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

Scoped

  • лучший вариант, когда вы хотите сохранить состояние в запросе.

одиночка

  • утечки памяти в этих сервисах будут накапливаться со временем.
  • Кроме того, эффективна память, поскольку они создаются после повторного использования везде.

Используйте Singletons там, где вам нужно поддерживать состояние приложения. Конфигурация приложения или параметры, служба регистрации, кэширование данных - вот некоторые примеры, где вы можете использовать синглтоны.

Внедрение службы с разными временами жизни в другой

  1. Никогда не вводите сервисы Scoped & Transient в сервис Singleton. (Это эффективно преобразует временный или ограниченный сервис в синглтон.)
  2. Никогда не вводите временные службы в ограниченную службу (это преобразует временную службу в определенную область).
Берекет Гебредингл
источник
Это лучший ответ. Мне нравится часть, где вы приводите примеры. Не так сложно понять, как они работают. Гораздо сложнее подумать, какой сервис куда ставить и как и когда очищать от них память. Было бы здорово, если бы вы объяснили больше об этом.
День святого Валентина
1

Как описано здесь (эта ссылка очень полезна) с примером,

Это отображение между интерфейсом и конкретным типом определяет, что каждый раз, когда вы запрашиваете тип IContryService, вы получаете новый экземпляр CountryService. Вот что означает переходный процесс в этом случае. Вы также можете добавлять одноэлементные сопоставления (используя AddSingleton) и сопоставления областей (используя AddScoped). В данном случае под областью действия подразумевается область действия для HTTP-запроса, что также означает, что он является одноэлементным во время выполнения текущего запроса. Вы также можете добавить существующий экземпляр в контейнер DI, используя метод AddInstance. Это почти полные способы регистрации на IServiceCollection.

Рехманали Момин
источник
1

Разница между AddSingleton против AddScoped против AddTransient

Регистрационные Услуги

Ядро ASP.NET предоставляет следующие 3 метода для регистрации служб в контейнере внедрения зависимостей. Метод, который мы используем, определяет время жизни зарегистрированного сервиса.

AddSingleton () - Как видно из названия, метод AddSingleton () создает службу Singleton. Сервис Singleton создается при первом запросе. Этот же экземпляр затем используется всеми последующими запросами. Таким образом, в общем случае служба Singleton создается только один раз для каждого приложения, и этот единственный экземпляр используется в течение всего срока службы приложения.

AddTransient () - этот метод создает временный сервис. Новый экземпляр временной службы создается каждый раз, когда она запрашивается.

AddScoped () - этот метод создает сервис Scoped. Новый экземпляр службы Scoped создается один раз для каждого запроса в области. Например, в веб-приложении оно создает 1 экземпляр для каждого http-запроса, но использует тот же экземпляр в других вызовах в рамках того же веб-запроса.

Александр Залдостанов
источник