AddTransient,AddScoped和AddSingleton服务的区别


934

我想在ASP.NET Core中实现依赖项注入(DI)。因此,将这段代码添加到ConfigureServices方法之后,两种方法都可以工作。

ASP.NET Core中services.AddTransientservice.AddScoped方法之间有什么区别?

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,这就是为什么我问这个问题。
Elvin Mammadov

5
晚会晚了,甚至在晚些时候都读了评论,但是我打印出了那篇文章,阅读了一下,并在空白处记下了同样的看法,现在我看到了@Neutrino。这篇文章完全模糊地提供了这一分析。值得庆幸的是,该示例不那么令人困惑。
Wellspring

5
据我了解:临时生命周期服务是在每次请求时创建的。这里所要求的单词是每天要求某事的英语含义,在这种情况下是一种服务。鉴于一词要求一次每个请求指的是HTTP请求。但我确实理解这种混淆。
Memet Olsen,

Answers:


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服务正在使用ID为的特定实例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以及一个请求OperationServiceIndex然后,该操作将显示所有控制器和服务的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值在请求内以及请求之间变化。

  • 瞬态对象总是不同的。为每个控制器和每个服务提供一个新实例。

  • 范围对象在一个请求中是相同的,但在不同的请求中是不同的

  • 每个对象和每个请求的单例对象都相同(无论是否在中提供了实例ConfigureServices


14
我理解它们每个的功能,但是有人可以解释使用其中一个而不是另一个的影响。如果使用不正确或选择一个而不是另一个,可能会导致什么问题。
尼泊尔尼泊尔

2
假设您正在创建具有单例作用域的请求上下文相关对象(例如当前用户),则它将在所有不需要的http请求中保持相同的实例。IOC都是关于创建实例的,因此我们需要指定所创建实例的范围。
akazemis

1
是!,我已经在主题顶部提到了链接!示例代码是从MS文档复制/粘贴的
akazemis,

1
谢谢。是的,无论会话/用户如何,整个应用程序中的单例都是相同的。显然,如果您的应用程序使用微服务架构,并且每个服务都在单独的进程中运行,则每个进程中的单例都相同
akazemis

1
您能给我们一个addTransient使用示例吗?因为在使用太多资源的情况下我没有找到任何实用程序来使用它
Terai

318

在.NET的依赖项注入中,有三个主要生存期:

Singleton,它将在整个应用程序中创建一个实例。它第一次创建实例,并在所有调用中重用相同的对象。

范围内的生命周期服务在范围内为每个请求创建一次。它等效于当前范围中的一个单例。例如,在MVC中,它为每个HTTP请求创建一个实例,但在同一Web请求中的其他调用中使用相同的实例。

每次请求时都会创建瞬态生存期服务。此生命周期最适合轻量级无状态服务。

在这里您可以找到示例,以了解两者之间的区别:

6个步骤的ASP.NET 5 MVC6依赖项注入(由于无效链接而导致的Web存档链接)

准备好依赖注入的ASP.NET:ASP.NET 5

这是官方文档的链接:

ASP.NET Core中的依赖注入


22
您能否解释一下为什么Transient最轻巧?我认为Transient是最繁重的工作,因为它需要为每次注入每次创建一个实例。
专家希望成为

17
你是对的。瞬态不是最轻量级的,我只是说它适合轻量级的RESTful服务:)
akazemis

3
因此,在哪种情况下,例如,如果要从数据库中检索几行,就可以在控制器示例中使用作用域和瞬态?在这种情况下,我试图了解范围与临时使用的情况。
–Sensei

4
这实际上取决于您期望的逻辑。例如,如果是单个数据库调用,则实际上与您使用的数据库没有任何区别。但是,如果您在同一请求中多次调用db,则可以使用作用域生存期,因为它在内存中保留了相同的存储库对象,并在同一Http Request上下文中多次重复使用。而瞬态对象会多次创建一个新的存储库对象(并消耗更多的内存)。如果您解释自己的特定情况,则很容易判断哪种方法更合适。
akazemis

2
这里要强调的重要一点是Singleton,Scoped和Transient就像俄罗斯的小药丸,一个在另一个之中。例如,嵌套时不可能颠倒它们的顺序。作用域或单例不能包含在Transient中,因为我们将延长违反包含的父项的寿命!
DL Narasimhan

34

当必须注入多个相同类型的对象时,可以在ASP.NET MVC核心DI中使用瞬态,作用域和单例定义对象创建过程。如果您不熟悉依赖项注入,可以观看此DI IoC视频

您可以看到以下控制器代码,其中我已在构造函数中请求了两个“ IDal”实例。瞬态,作用域和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图片

上面的图片是我在孟买接受ASP.NET MVC培训时由SBSS团队绘制的。非常感谢SBSS团队创建了以上图像。


9
这是我见过的有关瞬态服务的最复杂的解释。瞬变=解析此服务的任何时间都等同于分配变量new TService。Scoped将为该“作用域”(大多数情况下为HTTP请求)缓存其首次初始化。Singleton在应用程序的生存期内将仅缓存一个实例,就这么简单。上图是如此复杂。
Mardoxx

2
非常抱歉,我想我将用图表和代码快照使其更简单:-)但是,我的意思是正确的。
Shivprasad Koirala

30
  • Singleton是应用程序域生存期内的单个实例。
  • 作用域是作用域请求期间的单个实例,这意味着ASP.NET中的每个HTTP请求。
  • 瞬态是每个代码请求的单个实例。

通常,代码请求应通过构造函数参数进行,如

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

家庭控制器

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>

启动文件

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

复制粘贴此代码,然后按上的观点和开关按钮创建 AddSingletonAddScoped并且AddTransient每一次你会得到不同的结果,这将有助于你了解这样的解释:

AddSingleton() -顾名思义,AddSingleton()方法创建一个Singleton服务。首次请求时将创建一个Singleton服务。然后,所有后续请求都使用同一实例。因此,通常,每个应用程序仅创建一次Singleton服务,并且在整个应用程序生命周期内都使用单个实例。

AddTransient() -此方法创建一个Transient服务。每次请求时,都会创建一个新的瞬态服务实例。

AddScoped() -此方法创建一个范围服务。在范围内,每个请求都会创建一个范围服务的新实例。例如,在Web应用程序中,每个HTTP请求创建1个实例,但在同一Web请求内的其他调用中使用相同的实例。


2

使用哪一个

短暂的

  • 因为每次创建它们都会占用更多的内存和资源,并且会对性能产生负面影响
  • 将此状态用于状态很少或没有状态轻量级服务。

范围

  • 当您想要维护请求中的状态时,更好的选择。

辛格尔顿

  • 这些服务中的内存泄漏将随着时间的流逝而累积。
  • 一旦在任何地方重复使用它们,它们也可以提高内存效率。

在需要保持应用程序范围内的状态的地方使用Singletons。应用程序配置或参数,日志记录服务,数据缓存是可以使用单例的一些示例。

将不同生命周期的服务注入另一个

  1. 切勿将范围和临时服务注入Singleton服务。(这有效地将瞬态或作用域服务转换为单例。)
  2. 切勿将瞬态服务注入范围服务(这会将瞬态服务转换为范围服务。)

这是最好的答案。我喜欢您在其中举例的部分。了解它们的工作原理并不难。很难考虑将哪个服务放在哪里以及如何以及何时清除它们。如果您能对此进行更多解释,那将是很好的。
valentasm

1

如所描述的在这里(该链接是非常有用的)用一个例子,

接口和具体类型之间的映射定义为,每次您请求IContryService类型时,您都会获得CountryService的新实例。在这种情况下,这就是瞬态的意思。您还可以添加单例映射(使用AddSingleton)和作用域映射(使用AddScoped)。在这种情况下,作用域是指作用域为HTTP请求,这也意味着它在当前请求运行时是单例。您还可以使用方法AddInstance将现有实例添加到DI容器中。这些是注册IServiceCollection的几乎完整方法


0

AddSingleton与AddScoped与AddTransient之间的区别

注册服务

ASP.NET核心提供以下3种方法来向依赖项注入容器注册服务。我们使用的方法决定了注册服务的寿命。

AddSingleton()-顾名思义,AddSingleton()方法创建一个Singleton服务。首次请求时将创建一个Singleton服务。然后,所有后续请求都使用同一实例。因此,通常,每个应用程序仅创建一次Singleton服务,并且在整个应用程序生命周期内都使用单个实例。

AddTransient()-此方法创建一个Transient服务。每次请求时,都会创建一个新的瞬态服务实例。

AddScoped()-此方法创建一个范围服务。在范围内,每个请求都会创建一个范围服务的新实例。例如,在Web应用程序中,每个HTTP请求创建1个实例,但在同一Web请求内的其他调用中使用相同的实例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.