Differenze dei servizi AddTransient, AddScoped e AddSingleton


938

Voglio implementare l' iniezione delle dipendenze (DI) in ASP.NET Core. Quindi, dopo aver aggiunto questo codice al ConfigureServicesmetodo, funzionano in entrambi i modi.

Qual è la differenza tra i metodi services.AddTransiente service.AddScopedin ASP.NET Core?

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

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

92
@tmg I documenti dicono "I servizi a vita transitoria vengono creati ogni volta che vengono richiesti". e "I servizi a vita con ambito vengono creati una volta per richiesta". che a meno che la mia conoscenza dell'inglese non sia più debole di quanto pensassi significhi esattamente la stessa cosa.
Neutrino,

70
@tmg lo so. Sto solo sottolineando che i documenti non sono affatto chiari su questo punto, quindi indicare le persone ai documenti non è molto utile.
Neutrino,

13
@Neutrino, ecco perché ho posto questa domanda.
Elvin Mammadov,

5
In ritardo alla festa, leggendo i commenti anche più tardi, ma ho stampato l'articolo, l'ho letto e annotato la stessa osservazione a margine che ora vedo @Neutrino fatto qui. L'articolo era INTERAMENTE vago nell'offrire quell'analisi. L'esempio, per fortuna, era meno confuso.
Fonte

5
Per quanto ne so: i servizi a vita transitoria vengono creati ogni volta che vengono richiesti . La parola richiesta qui è il significato inglese quotidiano di chiedere qualcosa, in questo caso un servizio. Considerando che la parola richiesta in una volta per richiesta si riferisce a una richiesta HTTP. Ma capisco la confusione.
Memet Olsen

Risposte:


1655

TL; DR

Gli oggetti transitori sono sempre diversi; viene fornita una nuova istanza per ogni controller e ogni servizio.

Gli oggetti con ambito sono gli stessi all'interno di una richiesta, ma diversi tra richieste diverse.

Gli oggetti Singleton sono gli stessi per ogni oggetto e ogni richiesta.

Per ulteriori chiarimenti, questo esempio dalla documentazione ASP.NET mostra la differenza:

Per dimostrare la differenza tra queste opzioni di durata e di registrazione, prendere in considerazione una semplice interfaccia che rappresenta una o più attività come un'operazione con un identificatore univoco, OperationId. A seconda di come configuriamo la durata di questo servizio, il contenitore fornirà alla classe richiedente la stessa o diverse istanze del servizio. Per chiarire quale durata viene richiesta, creeremo un tipo per opzione di durata:

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

Implementiamo queste interfacce utilizzando una singola classe, Operationche accetta un GUID nel suo costruttore o utilizza un nuovo GUID se non ne viene fornito nessuno:

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

Successivamente, in ConfigureServices, ogni tipo viene aggiunto al contenitore in base alla sua durata:

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

Si noti che il IOperationSingletonInstanceservizio utilizza un'istanza specifica con un ID noto di Guid.Empty, quindi sarà chiaro quando questo tipo è in uso. Abbiamo anche registrato un OperationServiceparametro che dipende da ciascuno degli altri Operationtipi, in modo che all'interno di una richiesta sia chiaro se questo servizio sta ottenendo la stessa istanza del controller o una nuova per ciascun tipo di operazione. Tutto questo servizio non fa altro che esporre le sue dipendenze come proprietà, in modo che possano essere visualizzate nella vista.

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

Per dimostrare la durata dell'oggetto all'interno e tra singole richieste separate per l'applicazione, l'esempio include un OperationsControllerche richiede ogni tipo di IOperationtipo e un OperationService. L' Indexazione visualizza quindi tutti i OperationIdvalori del controller e del servizio .

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

Ora vengono fatte due richieste separate per questa azione del controller:

Prima richiesta

Seconda richiesta

Osservare quale dei OperationIdvalori varia all'interno di una richiesta e tra le richieste.

  • Gli oggetti transitori sono sempre diversi; viene fornita una nuova istanza per ogni controller e ogni servizio.

  • Gli oggetti con ambito sono gli stessi all'interno di una richiesta, ma diversi tra richieste diverse

  • Gli oggetti Singleton sono gli stessi per ogni oggetto e ogni richiesta (indipendentemente dal fatto che sia fornita un'istanza ConfigureServices)


14
Ho compreso le funzioni di ciascuno di essi, ma qualcuno può spiegare l'impatto dell'utilizzo dell'uno anziché dell'altro. Quali problemi può causare se non utilizzato correttamente o sceglierne uno anziché un altro.
pawan nepal

2
Supponiamo che tu stia creando un oggetto relativo al contesto della richiesta (come l'utente corrente) con ambito singleton, quindi rimarrà la stessa istanza in tutte le richieste http che non sono desiderate. Il COI riguarda la creazione di istanze, quindi è necessario specificare qual è l'ambito dell'istanza creata.
Akazemis

1
è !, ho citato il link in cima all'argomento! il codice di esempio viene copiato / incollato dai documenti MS
akazemis

1
Grazie. sì, singleton sarà lo stesso in tutta l'app indipendentemente dalla sessione / utente. ovviamente se la tua app utilizza l'architettura dei microservizi e ogni servizio viene eseguito in un processo separato, il singleton sarà lo stesso in ogni processo
Akazemis

1
Puoi darci un esempio di uso aggiuntivoTransient per favore? perché non ho trovato alcun programma di utilità per usarlo mentre utilizza troppe risorse
Terai

319

Nell'iniezione di dipendenze di .NET ci sono tre principali vite:

Singleton che crea una singola istanza in tutta l'applicazione. Crea l'istanza per la prima volta e riutilizza lo stesso oggetto in tutte le chiamate.

Scoped servizi a vita vengono creati una volta per richiesta nell'ambito. È equivalente a un singleton nell'ambito corrente. Ad esempio, in MVC crea un'istanza per ogni richiesta HTTP, ma utilizza la stessa istanza nelle altre chiamate all'interno della stessa richiesta Web.

I servizi a vita transitoria vengono creati ogni volta che vengono richiesti. Questa vita funziona meglio per servizi leggeri e senza stato.

Qui puoi trovare ed esempi per vedere la differenza:

ASP.NET 5 MVC6 Iniezione delle dipendenze in 6 passaggi (collegamento all'archivio Web a causa di un collegamento non funzionante)

ASP.NET pronto per l'iniezione di dipendenza: ASP.NET 5

E questo è il link alla documentazione ufficiale:

Iniezione delle dipendenze in ASP.NET Core


22
Potresti spiegare perché il Transient è il più leggero? Ho pensato che Transient fosse il lavoro più pesante perché ha bisogno di creare un'istanza ogni volta per ogni iniezione.
esperto vuole essere il

17
Hai ragione. Transient non è il più leggero, ho appena detto che è adatto a servizi RESTful leggeri :)
Akazemis

3
Quindi in quale scenario potremmo usare l'ambito e in quale transitorio nell'esempio del controller, ad esempio se stiamo recuperando poche righe dal database? In questo caso sto cercando di capire lo scenario di utilizzo con ambito transitorio o transitorio.
sensei,

4
dipende davvero dalla logica che ti aspetti. Ad esempio, se si tratta di una singola chiamata db in realtà non fa alcuna differenza quale si sta utilizzando. ma se si chiama db più volte nella stessa richiesta, è possibile utilizzare la durata con ambito, poiché mantiene lo stesso oggetto repository in memoria e riutilizza più volte nello stesso contesto di richiesta HTTP. Mentre quello transitorio crea più volte un nuovo oggetto repository (e consuma più memoria). Se spieghi il tuo scenario specifico, sarebbe facile giudicare quale si adatta meglio.
Akazemis,

3
Un punto importante da evidenziare qui è Singleton, Scoped e Transient sono come doills russi, uno dentro l'altro. Non è possibile invertire il loro ordine durante l'annidamento, ad es. un ambito o singleton non può essere contenuto in un Transitorio, perché estenderemmo la vita del genitore che va contro il contenimento!
DL Narasimhan,

34

Transitorio, con ambito e singleton definiscono il processo di creazione di oggetti nel core DI ASP.NET MVC quando devono essere iniettati più oggetti dello stesso tipo. Nel caso in cui non si abbia familiarità con l'iniezione delle dipendenze, è possibile visualizzare questo video DI IoC .

Puoi vedere il codice del controller di seguito in cui ho richiesto due istanze di "IDal" nel costruttore. Transient, Scoped e Singleton definiscono se la stessa istanza verrà iniettata in "_dal" e "_dal1" o diversi.

public class CustomerController : Controller
{
    IDal dal = null;

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

Transitorio: in transitorio, le nuove istanze di oggetto verranno iniettate in una singola richiesta e risposta. Di seguito è un'immagine di un'istantanea in cui ho visualizzato i valori GUID.

Inserisci qui la descrizione dell'immagine

Ambito: In ambito, la stessa istanza dell'oggetto verrà iniettata in una singola richiesta e risposta.

Inserisci qui la descrizione dell'immagine

Singleton: in singleton, lo stesso oggetto verrà iniettato in tutte le richieste e risposte. In questo caso verrà creata un'istanza globale dell'oggetto.

Di seguito è riportato un semplice diagramma che spiega visivamente quanto sopra fondamentale.

Immagine MVC DI

L'immagine sopra è stata disegnata dal team SBSS mentre stavo seguendo la formazione ASP.NET MVC a Mumbai . Un grande ringraziamento va al team SBSS per aver creato l'immagine sopra.


9
Questa è la spiegazione più complicata di un servizio temporaneo che abbia mai visto. Transitorio = Ogni volta che questo servizio viene risolto equivale ad assegnare la tua variabile new TService. Scoped memorizzerà nella cache la prima inizializzazione di tale "ambito" (richiesta http nella maggior parte dei casi). Singleton memorizzerà nella cache solo un'istanza per tutta la durata dell'applicazione, semplice come quella. I diagrammi di cui sopra sono così contorti.
Mardoxx

2
Mi dispiace, ho pensato di renderlo più semplice con diagrammi e istantanea del codice :-) Ma ho capito bene.
Shivprasad Koirala

30
  • Singleton è una singola istanza per la durata del dominio dell'applicazione.
  • Scoped è una singola istanza per la durata della richiesta con scope, che significa per richiesta HTTP in ASP.NET.
  • Transitorio è una singola istanza per richiesta di codice .

Normalmente la richiesta di codice dovrebbe essere fatta attraverso un parametro costruttore, come in

public MyConsumingClass(IDependency dependency)

Volevo sottolineare nella risposta di @ akazemis che i "servizi" nel contesto di DI non implicano servizi RESTful; i servizi sono implementazioni di dipendenze che forniscono funzionalità.


16

AddSingleton ()

AddSingleton () crea una singola istanza del servizio quando viene richiesto per la prima volta e riutilizza la stessa istanza in tutti i luoghi in cui è necessario quel servizio.

AddScoped ()

In un servizio con ambito, con ogni richiesta HTTP, otteniamo una nuova istanza. Tuttavia, all'interno della stessa richiesta HTTP, se il servizio è richiesto in più posizioni, come nella vista e nel controller, viene fornita la stessa istanza per l'intero ambito di tale richiesta HTTP. Ma ogni nuova richiesta HTTP riceverà una nuova istanza del servizio.

AddTransient ()

Con un servizio temporaneo, viene fornita una nuova istanza ogni volta che viene richiesta un'istanza del servizio nell'ambito della stessa richiesta HTTP o tra richieste HTTP diverse.


5

Dopo aver cercato una risposta a questa domanda, ho trovato una brillante spiegazione con un esempio che vorrei condividere con te.

Puoi guardare un video che dimostra le differenze QUI

In questo esempio abbiamo questo codice dato:

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

Crea vista

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

Copia e incolla questo codice e premi il pulsante Crea nella vista e passa tra AddSingleton, AddScopede AddTransientotterrai ogni volta un risultato diverso che ti aiuterà a capire questa spiegazione:

AddSingleton () - Come suggerisce il nome, il metodo AddSingleton () crea un servizio Singleton. Un servizio Singleton viene creato quando viene richiesto per la prima volta. Questa stessa istanza viene quindi utilizzata da tutte le richieste successive. Quindi, in generale, un servizio Singleton viene creato una sola volta per applicazione e quella singola istanza viene utilizzata per tutta la durata dell'applicazione.

AddTransient () : questo metodo crea un servizio temporaneo . Una nuova istanza di un servizio temporaneo viene creata ogni volta che viene richiesta.

AddScoped () : questo metodo crea un servizio con ambito. Una nuova istanza di un servizio con ambito viene creata una volta per richiesta all'interno dell'ambito. Ad esempio, in un'applicazione Web crea 1 istanza per ogni richiesta http ma utilizza la stessa istanza nelle altre chiamate all'interno della stessa richiesta Web.


2

Quale usare

transitorio

  • poiché vengono creati ogni volta che useranno più memoria e risorse e possono avere un impatto negativo sulle prestazioni
  • usalo per un servizio leggero con poco o nessun stato .

Scoped

  • opzione migliore quando si desidera mantenere lo stato all'interno di una richiesta.

Singleton

  • le perdite di memoria in questi servizi si accumuleranno nel tempo.
  • anche memoria efficiente in quanto vengono creati una volta riutilizzati ovunque.

Utilizzare Singletons dove è necessario mantenere lo stato esteso dell'applicazione. Configurazione dell'applicazione o parametri, servizio di registrazione, memorizzazione nella cache dei dati sono alcuni degli esempi in cui è possibile utilizzare singleton.

Iniettare un servizio con vite diverse in un altro

  1. Non iniettare mai servizi Scoped & Transient nel servizio Singleton. (Ciò converte efficacemente il servizio temporaneo o con ambito nel singleton.)
  2. Non iniettare mai servizi transitori nel servizio con ambito (Questo converte il servizio transitorio nel campo con ambito.)

Questa è la risposta migliore Mi piace la parte in cui fai degli esempi. Non è così difficile capire come funzionano. È molto più difficile pensare a quale servizio mettere e come e quando ripulirli dalla memoria. Sarebbe bello se spiegassi di più al riguardo.
valentasm

1

Come descritto qui (questo link è molto utile) con un esempio,

Questa mappatura tra l'interfaccia e il tipo concreto definisce che ogni volta che richiedi un tipo di IContryService, otterrai una nuova istanza di CountryService. Questo è ciò che significa transitorio in questo caso. È anche possibile aggiungere mappature singleton (utilizzando AddSingleton) e mappature con ambito (utilizzando AddScoped). Ambito in questo caso significa ambito per una richiesta HTTP, il che significa anche che è un singleton mentre la richiesta corrente è in esecuzione. È inoltre possibile aggiungere un'istanza esistente al contenitore DI utilizzando il metodo AddInstance. Questi sono i modi quasi completi per registrarsi a IServiceCollection


1

La differenza tra AddSingleton vs AddScoped vs AddTransient

Registrazione dei servizi

Il core ASP.NET fornisce i seguenti 3 metodi per registrare i servizi con il contenitore di iniezione delle dipendenze. Il metodo che utilizziamo determina la durata del servizio registrato.

AddSingleton () - Come suggerisce il nome, il metodo AddSingleton () crea un servizio Singleton. Un servizio Singleton viene creato quando viene richiesto per la prima volta. Questa stessa istanza viene quindi utilizzata da tutte le richieste successive. Quindi, in generale, un servizio Singleton viene creato una sola volta per applicazione e quella singola istanza viene utilizzata per tutta la durata dell'applicazione.

AddTransient (): questo metodo crea un servizio temporaneo . Una nuova istanza di un servizio temporaneo viene creata ogni volta che viene richiesta.

AddScoped (): questo metodo crea un servizio con ambito. Una nuova istanza di un servizio con ambito viene creata una volta per richiesta all'interno dell'ambito. Ad esempio, in un'applicazione Web crea 1 istanza per ogni richiesta http ma utilizza la stessa istanza nelle altre chiamate all'interno della stessa richiesta Web.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.