Відмінності послуг AddTransient, AddScoped та AddSingleton


937

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

Яка різниця між методами services.AddTransientта service.AddScopedядрами в ASP.NET Core?

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

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}

92
@tmg Документи говорять, що "Перехідні служби протягом усього життя створюються щоразу, коли їх запитують". та "Обсяг службового обслуговування створюється один раз на запит." що, якщо моє розуміння англійської мови слабше, ніж я думав, насправді означає саме те саме.
Нейтрино

70
@tmg Я знаю. Я просто вказую, що документи в цьому питанні зовсім не зрозумілі, тому вказівка ​​людей на документи не дуже корисна.
Нейтрино

13
@Neutrino, саме тому я задав це питання.
Елвін Мамедов

5
Пізно до вечірки, прочитавши коментарі ще пізніше, але я надрукував цю статтю, прочитав її і прописав те саме спостереження в полі, що зараз бачу @Neutrino, зроблене тут. Стаття була НЕПАСНО розпливчастою, пропонуючи цей аналіз. Приклад, на щастя, був менш заплутаним.
Wellspring

5
Наскільки я розумію: послуги перехідного життя створюються щоразу, коли їх запитують . Слово, яке тут запитується, - це повсякденне англійське значення просити щось, в даному випадку послуги. У той час як слова запиту в один раз запит в ставиться до HTTP - запит. Але я розумію знущання.
Memet Olsen

Відповіді:


1648

TL; DR

Перехідні об'єкти завжди різні; новий екземпляр надається кожному контролеру та кожній службі.

Об'єкти, що охоплюють область, однакові в запиті, але різні в різних запитах.

Одиничні об'єкти однакові для кожного об'єкта та кожного запиту.

Для додаткового уточнення цей приклад із документації 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типу, а також an 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)


14
Я зрозумів функції кожного з них, але чи може хтось пояснити вплив використання одного замість іншого. Які проблеми можуть викликати, якщо їх не використовувати правильно або вибрати одну замість іншої.
pawan nepal

2
Скажімо, ви створюєте об'єкт, пов’язаний з контекстом запиту (як поточний користувач) з областю singleton, тоді він залишатиметься тим самим екземпляром для всіх запитів http, який не бажаний. IOC стосується створення екземплярів, тому нам потрібно вказати, яка сфера створеного екземпляра.
akazemis

1
так !, я згадував посилання у верхній частині теми! зразок коду копіюється / вставляється з MS docs
akazemis

1
Дякую. так, синглтон буде однаковим у всьому додатку незалежно від сеансу / користувача. Очевидно, якщо ваш додаток використовує архітектуру мікросервісів і кожна служба працює в окремому процесі, синглтон буде однаковий у кожному процесі
akazemis

1
Чи можете ви надати нам приклад використання addTransient? тому що я не знайшов жодних утиліт, щоб використовувати його, хоча він використовує занадто багато ресурсів
Terai

318

У ін'єкції залежності .NET є три основні терміни життя:

Синглтон, який створює один екземпляр у всій програмі. Він створює екземпляр вперше і повторно використовує той самий об'єкт у всіх викликах.

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

Служби перехідного життя створюються щоразу, коли їх запитують. Цей термін експлуатації найкраще підходить для легких послуг без громадянства.

Тут ви можете знайти та приклади, щоб побачити різницю:

ASP.NET 5 MVC6 введення залежності в 6 кроків (посилання веб-архіву через мертве посилання)

Ваша ін'єкційна залежність готова ASP.NET: ASP.NET 5

І це посилання на офіційну документацію:

Введення залежності в ASP.NET Core


22
Скажіть, будь ласка, чому «Тимчасовий» є найбільш легким? Я подумав, що "Перехідний" - це найважча робота, тому що йому потрібно створювати екземпляр щоразу для кожної ін'єкції.
Експерт хоче бути


3
Отже, у якому сценарії ми могли б використовувати масштабований, а в якому перехідний у прикладі контролера, наприклад, якщо ми отримуємо кілька рядків із бази даних? Я намагаюся розібратися в такому випадку щодо масштабування та перехідного сценарію використання.
Сенсей

4
це дійсно залежить від логіки, яку ви очікуєте. Наприклад, якщо це єдиний db-дзвінок, він насправді не має значення, який саме ви використовуєте. але якщо ви викликаєте db кілька разів в одному запиті, ви можете використовувати масштабний термін експлуатації, оскільки він зберігає один і той же об’єкт сховища в пам’яті і повторно використовує в одному контексті Http Request. Тоді як перехідний створює новий об'єкт сховища кілька разів (і витрачає більше пам’яті). Якщо ви поясните свій конкретний сценарій, було б легко визначити, який з них підходить краще.
akazemis

2
Одним важливим моментом, який слід виділити тут, є Singleton, Scoped та Transient, як російські ляльки, одна в іншій. Неможливо змінити їх порядок під час введення, наприклад, наприклад. скопійований або одиночний не може міститися в Перехідному, тому що ми би продовжили життя батьків, що суперечить обмеженню!
DL Нарасимхан

34

Перехідний, масштабний та однотонний визначають процес створення об'єктів в ядрі DI ASP.NET MVC, коли потрібно вводити кілька об'єктів одного типу. Якщо ви не знайшли ін'єкцій у наркозалежність, ви можете переглянути це відеоролик DI IoC .

Нижче ви можете побачити код контролера, в якому я попросив два екземпляри "IDal" у конструкторі. Тимчасові, 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.

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

Область застосування: У межах області масштабування одного і того ж об’єкта буде введено один запит та відповідь.

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

Синглтон: У одиночному одному об'єкті буде введено один і той же об'єкт у всі запити та відповіді. У цьому випадку буде створений один глобальний примірник об’єкта.

Нижче наведена проста схема, яка наочно пояснює вищезазначене.

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

Наведене вище зображення було намальовано командою SBSS, коли я проходив навчання ASP.NET MVC у Мумбаї . Велика подяка віддається команді SBSS за створення вищезазначеного образу.


9
Це єдине найскладніше пояснення перехідної служби, яку я коли-небудь бачив. Тимчасовий = Кожен раз, коли ця послуга вирішена, це еквівалент призначенню вашої змінної new TService. Scoped буде кешувати першу ініціалізацію для цього "області" (http запит у більшості випадків). Singleton буде кешувати лише один екземпляр протягом життя програми, як простий. Наведені діаграми настільки вивернуті.
Мардокс

2
Так вибачте, що я думав, що я зроблю це простіше з діаграмами та знімком коду :-) Але я все-таки зрозумію вашу думку.
Шивпрасад Койрала

30
  • Singleton - це єдиний екземпляр протягом життя домену програми.
  • Scoped - це єдиний екземпляр протягом тривалості масштабованого запиту, що означає запит HTTP в ASP.NET.
  • Перехідний - це один екземпляр на запит коду .

Зазвичай запит на код повинен бути зроблений через параметр конструктора, як у

public MyConsumingClass(IDependency dependency)

Я хотів зазначити у відповіді @ akazemis, що "послуги" в контексті DI не передбачають RESTful послуг; послуги - це реалізація залежностей, що забезпечують функціональність.


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 () - Цей метод створює послугу з розширенням. Новий екземпляр послуги з розширенням створюється один раз на запит у межах області. Наприклад, у веб-програмі він створює 1 екземпляр на кожен запит http, але використовує той самий екземпляр в інших викликах у межах цього ж веб-запиту.


2

Який використовувати

Тимчасовий

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

Обсяг

  • кращий варіант, коли ви хочете підтримувати стан у запиті.

Сінглтон

  • З плином часу витоки пам'яті в цих службах накопичуватимуться.
  • також ефективної пам'яті, оскільки вони створюються, коли вони повторно використовуються скрізь.

Використовуйте 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 () - Цей метод створює послугу з розширенням. Новий екземпляр послуги з розширенням створюється один раз на запит у межах області. Наприклад, у веб-програмі він створює 1 екземпляр на кожен запит http, але використовує той самий екземпляр в інших викликах у межах цього ж веб-запиту.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.