Różnice w usługach AddTransient, AddScoped i AddSingleton

937

Chcę zaimplementować wstrzykiwanie zależności (DI) w programie ASP.NET Core. Po dodaniu tego kodu do ConfigureServicesmetody działają obie metody.

Jaka jest różnica między metodami services.AddTransienti service.AddScopedw programie ASP.NET Core?

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

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}
Elvin Mammadov
źródło
92
@tmg Dokumenty mówią: „Za każdym razem, gdy są wymagane, tworzone są przejściowe usługi na całe życie”. oraz „Dożywotnie usługi o określonym zakresie są tworzone raz na żądanie”. co, chyba że rozumiem angielski, jest słabsze, niż myślałem, że znaczy dokładnie to samo.
Neutrino,
70
@tmg Wiem. Po prostu wskazuję, że dokumenty nie są w tej kwestii jasne, więc wskazywanie ludziom dokumentów nie jest zbyt pomocne.
Neutrino,
13
@Neutrino, dlatego zadałem to pytanie.
Elvin Mammadov,
5
Późno na imprezę, czytając komentarze jeszcze później, ale wydrukowałem ten artykuł, przeczytałem go i zanotowałem tę samą obserwację na marginesie, którą teraz widzę tutaj @Neutrino. Artykuł był CAŁKOWICIE niejasny w oferowaniu tej analizy. Przykład, na szczęście, był mniej mylący.
Wellspring
5
O ile rozumiem: przejściowe usługi na całe życie są tworzone za każdym razem, gdy są wymagane . Słowo, o które się tu prosi , to codzienne angielskie znaczenie pytania o coś, w tym przypadku usługi. Podczas gdy słowo zamówienie w raz za wniosek odnosi się do żądania HTTP. Ale rozumiem konflikt.
Memet Olsen,

Odpowiedzi:

1648

TL; DR

Obiekty przejściowe są zawsze różne; nowa instancja jest dostarczana do każdego kontrolera i każdej usługi.

Obiekty o zasięgu są takie same w żądaniu, ale różne dla różnych żądań.

Obiekty Singleton są takie same dla każdego obiektu i każdego żądania.

Aby uzyskać dodatkowe wyjaśnienia, ten przykład z dokumentacji ASP.NET pokazuje różnicę:

Aby wykazać różnicę między tymi opcjami życiowych i rejestracyjnych, rozważmy prosty interfejs, który stanowi jeden lub więcej zadań jako operacja z unikalnym identyfikatorem, OperationId. W zależności od tego, w jaki sposób skonfigurujemy czas życia tej usługi, kontener zapewni klasę żądającą takie same lub różne wystąpienia usługi. Aby wyjaśnić, który okres istnienia jest wymagany, utworzymy jeden typ dla okresu istnienia:

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
    {
    }
}

Implementujemy te interfejsy za pomocą jednej klasy, Operationktóra akceptuje identyfikator GUID w swoim konstruktorze lub używa nowego identyfikatora GUID, jeśli nie został podany:

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;
    }
}

Następnie za ConfigureServiceskażdy typ jest dodawany do kontenera zgodnie z jego nazwanym czasem życia:

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

Pamiętaj, że IOperationSingletonInstanceusługa korzysta z określonej instancji o znanym identyfikatorze Guid.Empty, więc będzie jasne, kiedy ten typ jest używany. Zarejestrowaliśmy również opcję OperationServicezależną od każdego z pozostałych Operationtypów, dzięki czemu w żądaniu będzie jasne, czy ta usługa otrzymuje tę samą instancję, co kontroler, czy nową, dla każdego typu operacji. Wszystko, co robi ta usługa, ujawnia swoje zależności jako właściwości, dzięki czemu można je wyświetlać w widoku.

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;
        }
    }
}

Aby zademonstrować czasy życia obiektu w ramach oddzielnych indywidualnych żądań do aplikacji i pomiędzy nimi, próbka zawiera OperationsControllerżądanie, które żąda każdego rodzaju, IOperationjak również OperationService. Następnie Indexakcja wyświetla wszystkie wartości kontrolera i usługi 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();
        }
    }
}

Teraz wysyłane są dwa osobne żądania do tej akcji kontrolera:

Pierwsza prośba

Druga prośba

Obserwuj, które OperationIdwartości różnią się w obrębie żądania i między żądaniami.

  • Obiekty przejściowe są zawsze różne; nowa instancja jest dostarczana do każdego kontrolera i każdej usługi.

  • Obiekty o zasięgu są takie same w żądaniu, ale różne dla różnych żądań

  • Obiekty Singleton są takie same dla każdego obiektu i każdego żądania (niezależnie od tego, czy instancja jest dostarczona ConfigureServices)

akazemis
źródło
14
Zrozumiałem funkcje każdego z nich, ale czy ktoś może wyjaśnić wpływ używania jednego zamiast drugiego. Jakie problemy może to spowodować, jeśli nie zostanie prawidłowo użyte lub wybierz jedno zamiast drugiego.
pawan nepal
2
Załóżmy, że tworzysz obiekt związany z kontekstem żądania (jak bieżący użytkownik) o zasięgu singleton, to pozostanie taki sam obiekt dla wszystkich żądań HTTP, co nie jest pożądane. W IOC chodzi o tworzenie instancji, dlatego musimy określić, jaki jest zakres tworzonej instancji.
akazemis,
1
to jest !, wspomniałem link na górze tematu! przykładowy kod jest kopiowany / wklejany z dokumentów MS
akazemis
1
dzięki. tak singleton będzie taki sam w całej aplikacji, niezależnie od sesji / użytkownika. oczywiście jeśli twoja aplikacja korzysta z architektury mikrousług, a każda usługa działa w osobnym procesie, singleton będzie taki sam w każdym procesie
akazemis
1
Czy możesz podać nam przykład zastosowania przejściowego? ponieważ nie znalazłem żadnych narzędzi do korzystania z niego, gdy
zużywa
318

W wstrzykiwaniu zależności platformy .NET istnieją trzy główne okresy istnienia:

Singleton, który tworzy pojedyncze wystąpienie w całej aplikacji. Tworzy instancję po raz pierwszy i ponownie wykorzystuje ten sam obiekt we wszystkich wywołaniach.

Dożywotnie usługi o określonym zakresie są tworzone raz na żądanie w ramach zakresu. Jest to ekwiwalent singletonu w obecnym zakresie. Na przykład w MVC tworzy jedno wystąpienie dla każdego żądania HTTP, ale używa tego samego wystąpienia w innych wywołaniach w ramach tego samego żądania internetowego.

Przejściowe dożywotnie usługi są tworzone za każdym razem, gdy są wymagane. Ten okres użytkowania działa najlepiej w przypadku lekkich, bezpaństwowych usług.

Tutaj możesz znaleźć i przykłady, aby zobaczyć różnicę:

Wstrzykiwanie zależności ASP.NET 5 MVC6 w 6 krokach (link do archiwum internetowego z powodu martwego łącza)

Twój gotowy do wstrzykiwania zależności ASP.NET: ASP.NET 5

A to jest link do oficjalnej dokumentacji:

Wstrzykiwanie zależności w programie ASP.NET Core

akazemis
źródło
22
Czy możesz wyjaśnić, dlaczego Transient jest najlżejszy? Myślałem, że przemijanie jest najcięższą pracą, ponieważ musi tworzyć instancję za każdym razem dla każdego zastrzyku.
Ekspert chce być
17
Masz rację. Przejściowy nie jest
najlżejszy
3
Więc w którym scenariuszu moglibyśmy zastosować zakres, a w którym stan przejściowy na przykład w kontrolerze, na przykład, jeśli pobieramy kilka wierszy z bazy danych? W tym przypadku staram się zrozumieć scenariusz użycia z zakresem vs przejściowe użycie.
sensei
4
to naprawdę zależy od logiki, której oczekujesz. Na przykład, jeśli jest to pojedyncze wywołanie db, tak naprawdę nie ma znaczenia, którego używasz. ale jeśli wywołujesz db wiele razy w tym samym żądaniu, możesz użyć okresu istnienia, ponieważ utrzymuje ten sam obiekt repozytorium w pamięci i wielokrotnie używa ponownie w tym samym kontekście żądania HTTP. Tymczasem przejściowy tworzy wielokrotnie nowy obiekt repozytorium (i zużywa więcej pamięci). Jeśli wyjaśnisz konkretny scenariusz, łatwo będzie ocenić, który z nich jest lepszy.
akazemis,
2
Jednym ważnym punktem do podkreślenia tutaj jest Singleton, Scoped i Transient są jak rosyjskie doills, jeden w drugim. Podczas zagnieżdżania nie jest możliwe odwrócenie ich kolejności, np. Scope lub singleton nie mogą być zawarte w Transient, ponieważ przedłużalibyśmy życie rodzica, co byłoby sprzeczne z ograniczeniem!
DL Narasimhan
34

Transient, scoped i singleton definiują proces tworzenia obiektów w ASP DINET MVC core DI, gdy trzeba wstrzyknąć wiele obiektów tego samego typu. Jeśli dopiero zaczynasz przygodę z iniekcją zależności, możesz zobaczyć wideo DI IoC .

Możesz zobaczyć poniższy kod kontrolera, w którym zażądałem dwóch wystąpień „IDal” w konstruktorze. Transient, Scoped i Singleton określają, czy to samo wystąpienie zostanie wstrzyknięte w „_dal” i „_dal1”, czy w inne.

public class CustomerController : Controller
{
    IDal dal = null;

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

Przejściowy: W stanach przejściowych nowe instancje obiektów zostaną wstrzyknięte w jednym żądaniu i odpowiedzi. Poniżej znajduje się migawka, na której wyświetlałem wartości GUID.

Wpisz opis zdjęcia tutaj

Zakres: W zakresie zakres tego samego wystąpienia obiektu zostanie wstrzyknięty w jednym żądaniu i odpowiedzi.

Wpisz opis zdjęcia tutaj

Singleton: W singletonie ten sam obiekt zostanie wstrzyknięty we wszystkie żądania i odpowiedzi. W takim przypadku zostanie utworzona jedna globalna instancja obiektu.

Poniżej znajduje się prosty schemat, który wyjaśnia powyższy fundament wizualnie.

Obraz MVC DI

Powyższe zdjęcie zostało narysowane przez zespół SBSS, kiedy brałem udział w szkoleniu ASP.NET MVC w Bombaju . Ogromne podziękowania należą się zespołowi SBSS za stworzenie powyższego obrazu.

Shivprasad Koirala
źródło
9
Jest to najbardziej skomplikowane wyjaśnienie przejściowej usługi, jakie kiedykolwiek widziałem. Transient = Za każdym razem, gdy ta usługa zostanie rozwiązana, jest to odpowiednik przypisania zmiennej new TService. Scoped zbuforuje pierwszą inicjalizację tego „zakresu” (w większości przypadków żądanie HTTP). Singleton będzie buforował tylko jedno wystąpienie w całym okresie istnienia aplikacji, to proste. Powyższe diagramy są bardzo skomplikowane.
Mardoxx
2
Więc przepraszam, pomyślałem, że ułatwię to dzięki diagramom i migawkom kodu :-) Ale rozumiem o co ci chodzi.
Shivprasad Koirala
30
  • Singleton to pojedyncze wystąpienie na cały okres istnienia domeny aplikacji.
  • Zakres obejmuje pojedyncze wystąpienie na czas trwania zakresu, co oznacza jedno żądanie HTTP w programie ASP.NET.
  • Transient to jedna instancja na żądanie kodu .

Normalnie żądanie kodu powinno być wykonane za pomocą parametru konstruktora, jak w

public MyConsumingClass(IDependency dependency)

Chciałem wskazać w odpowiedzi @ akazemis, że „usługi” w kontekście DI nie oznaczają usług RESTful; usługi są implementacjami zależności zapewniających funkcjonalność.

użytkownik1969177
źródło
16

AddSingleton ()

AddSingleton () tworzy pojedyncze wystąpienie usługi, gdy jest najpierw żądane, i ponownie wykorzystuje to samo wystąpienie we wszystkich miejscach, w których ta usługa jest potrzebna.

AddScoped ()

W usłudze o zasięgu z każdym żądaniem HTTP otrzymujemy nową instancję. Jednak w ramach tego samego żądania HTTP, jeśli usługa jest wymagana w wielu miejscach, np. W widoku i kontrolerze, to ta sama instancja jest zapewniona dla całego zakresu tego żądania HTTP. Ale każde nowe żądanie HTTP otrzyma nową instancję usługi.

AddTransient ()

W przypadku usługi przejściowej nowa instancja jest dostarczana za każdym razem, gdy żądana jest instancja usługi, niezależnie od tego, czy jest objęta zakresem tego samego żądania HTTP, czy też różnych żądań HTTP.

Yasser Shaikh
źródło
5

Po znalezieniu odpowiedzi na to pytanie znalazłem genialne wyjaśnienie z przykładem, którym chciałbym się z tobą podzielić.

Możesz obejrzeć film pokazujący różnice TUTAJ

W tym przykładzie mamy następujący kod:

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();
    }
}

Utwórz widok

@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>();
}

Skopiuj i wklej ten kod i naciśnij przycisk Utwórz w widoku i przełączaj się między nimi AddSingleton, AddScopeda AddTransientza każdym razem otrzymasz inny wynik, który może pomóc w zrozumieniu tego wyjaśnienia:

AddSingleton () - Jak sama nazwa wskazuje, metoda AddSingleton () tworzy usługę Singleton. Usługa Singleton jest tworzona przy pierwszym żądaniu. Ta sama instancja jest następnie używana przez wszystkie kolejne żądania. Ogólnie rzecz biorąc, usługa Singleton jest tworzona tylko raz na aplikację i ta pojedyncza instancja jest używana przez cały czas życia aplikacji.

AddTransient () - Ta metoda tworzy usługę przejściową. Nowe wystąpienie usługi przejściowej jest tworzone za każdym razem, gdy jest ono wymagane.

AddScoped () - Ta metoda tworzy usługę o zasięgu. Nowa instancja usługi o zasięgu jest tworzona raz na żądanie w ramach zakresu. Na przykład w aplikacji internetowej tworzy 1 wystąpienie na każde żądanie HTTP, ale używa tego samego wystąpienia w innych wywołaniach w ramach tego samego żądania internetowego.

Offir Pe'er
źródło
2

Którego użyć

Przemijający

  • ponieważ są tworzone za każdym razem, będą zużywać więcej pamięci i zasobów i mogą mieć negatywny wpływ na wydajność
  • użyj tego do lekkiej usługi z niewielkim stanem lub bez stanu .

Zakres

  • lepsza opcja, jeśli chcesz utrzymać stan w żądaniu.

Singel

  • wycieki pamięci w tych serwisach będą się z czasem powiększały.
  • również wydajna pamięć, ponieważ są tworzone raz wszędzie używane.

Używaj singletonów, gdy chcesz utrzymać stan aplikacji. Konfiguracja lub parametry aplikacji, usługa rejestrowania, buforowanie danych to tylko niektóre z przykładów użycia singletonów.

Wstrzykiwanie usługi o różnych cyklach życia do innej

  1. Nigdy nie wprowadzaj usług Scoped & Transient do usługi Singleton. (To skutecznie przekształca usługę przejściową lub zakresową w singleton.)
  2. Nigdy nie wstrzykuj usług przejściowych do usługi o zasięgu (zamienia to usługę przejściową w zakres).
bereket gebredingle
źródło
To najlepsza odpowiedź. Lubię część, w której podajesz przykłady. Nie jest trudno zrozumieć, jak działają. O wiele trudniej jest zastanowić się, którą usługę umieścić i jak i kiedy wyczyścić pamięć. Byłoby wspaniale, gdybyś wyjaśnił o tym więcej.
walentasm
1

Jak opisano tutaj (ten link jest bardzo przydatny) z przykładem,

To odwzorowanie między interfejsem a konkretnym typem definiuje, że za każdym razem, gdy poprosisz o typ IContryService, otrzymasz nową instancję CountryService. To właśnie oznacza przemijanie w tym przypadku. Możesz także dodawać mapowania singletonów (przy użyciu AddSingleton) i mapowania o zasięgu (przy użyciu AddScoped). Zakres w tym przypadku oznacza zakres do żądania HTTP, co oznacza również, że jest to singleton podczas działania bieżącego żądania. Możesz także dodać istniejącą instancję do kontenera DI, używając metody AddInstance. Są to prawie kompletne sposoby rejestracji w IServiceCollection

Rehmanali Momin
źródło
1

Różnica między AddSingleton a AddScoped vs AddTransient

Rejestracja usług

Program ASP.NET core udostępnia 3 następujące metody rejestrowania usług w kontenerze wstrzykiwania zależności. Stosowana przez nas metoda określa czas życia zarejestrowanej usługi.

AddSingleton () - Jak sama nazwa wskazuje, metoda AddSingleton () tworzy usługę Singleton. Usługa Singleton jest tworzona przy pierwszym żądaniu. Ta sama instancja jest następnie używana przez wszystkie kolejne żądania. Ogólnie rzecz biorąc, usługa Singleton jest tworzona tylko raz na aplikację i ta pojedyncza instancja jest używana przez cały czas życia aplikacji.

AddTransient () - Ta metoda tworzy usługę przejściową. Nowe wystąpienie usługi przejściowej jest tworzone za każdym razem, gdy jest ono wymagane.

AddScoped () - Ta metoda tworzy usługę o zasięgu. Nowa instancja usługi o zasięgu jest tworzona raz na żądanie w ramach zakresu. Na przykład w aplikacji internetowej tworzy 1 wystąpienie na każde żądanie HTTP, ale używa tego samego wystąpienia w innych wywołaniach w ramach tego samego żądania internetowego.

Alexander Zaldostanov
źródło