Add瞬态、AddScoped和AddSingleton服务的差异

我想在ASP.NETCore中实现依赖注入(DI)。因此,在将此代码添加到#0方法后,两种方式都有效。

ASP.NETCore中的services.AddTransientservice.AddScoped方法有什么区别?

public void ConfigureServices(IServiceCollection services){// Add framework services.
// Add application services.services.AddTransient<IEmailSender, AuthMessageSender>();services.AddScoped<IEmailSender, AuthMessageSender>();}
1109468 次浏览

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

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

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

瞬态生命周期服务每次被请求时都会被创建。这个生命周期最适合轻量级、无状态的服务。

在这里你可以找到和例子来看看区别:

ASP.NET5个MVC6依赖注入步骤(由于死链接导致的网络存档链接)

您的依赖注入就绪ASP.NET:ASP.NET5

这是官方留档的链接:

依赖注入ASP.NET核心

太长别读

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

作用域对象在请求中相同,但在请求中不同不同的请求

单例对象对于每个对象和每个请求都是相同的。

为了进一步澄清,这个来自. 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,则使用新的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的特定实例,因此当使用这种类型时会很清楚。我们还注册了一个依赖于其他Operation类型的OperationService,因此在请求中可以清楚地看到,对于每种操作类型,该服务是获得与控制器相同的实例,还是获得新的实例。这个服务所做的只是将其依赖项公开为属性,这样它们就可以显示在视图中。

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 servicesViewBag.Transient = _transientOperation;ViewBag.Scoped = _scopedOperation;ViewBag.Singleton = _singletonOperation;ViewBag.SingletonInstance = _singletonInstanceOperation;
// Operation service has its own requested servicesViewBag.Service = _operationService;return View();}}}

现在对这个控制器操作发出两个单独的请求:

第一个请求

第二次请求

观察OperationId中哪些值在请求内以及请求之间发生变化。

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

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

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

瞬态,范围单例定义了当必须注入多个相同类型的对象时ASP.NETMVC核心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值。

在此处输入图片描述

范围:在作用域中,相同的对象实例将被注入到单个请求和响应中。

在此处输入图片描述

单例:在单例中,相同的对象将在所有请求和响应中注入。在这种情况下,将创建一个对象的全局实例。

下面是一个简单的图表,直观地解释了上述基本原理。

在此处输入图片描述

上面的图片是由SBSS团队在我拍摄ASP.NETMVC培训在孟买时绘制的。非常感谢SBSS团队创建了上面的图片。

  • Singleton是应用程序生命周期的单个实例域名。
  • 作用域是作用域持续时间的单个实例请求,这意味着每个超文本传输协议请求在ASP.NET.
  • 瞬态是每个代码请求的单个实例。

通常,代码请求应通过构造函数参数发出,如

public MyConsumingClass(IDependency dependency)

我想在@akazemis的回答中指出,DI上下文中的“服务”并不意味着RESTful服务;服务是提供功能的依赖项的实现。

添加单例

AddSingleton()在首次请求时创建服务的单个实例,并在需要该服务的所有地方重用该实例。

范围定义

在作用域服务中,每次HTTP请求,我们都会得到一个新实例。然而,在同一个HTTP请求中,如果多个地方需要该服务,比如视图和控制器中,那么该HTTP请求的整个作用域都会提供同一个实例。但是每个新的HTTP请求都将获得服务的新实例。

添加瞬态函数

对于瞬态服务,每次请求服务实例时都会提供一个新实例,无论它是在同一个HTTP请求的范围内还是跨不同的HTTP请求。

在寻找这个问题的答案后,我找到了一个很好的解释,我想与你分享一个例子。

您可以观看演示差异的视频<强>这里

在这个例子中,我们有这样的代码:

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

复制粘贴此代码并按下视图中的创建按钮并在AddSingletonAddScopedAddTransient每次你都会得到一个不同的结果,这可能会帮助你理解这一点。

添加单例-顾名思义,AddSingleton()方法创建了一个Singleton服务。Singleton服务在第一次创建时被创建请求。然后,这个相同的实例被所有后续所以一般来说,Singleton服务只创建一次每个应用程序,并且该单个实例在整个应用程序生命周期。

添加瞬态函数-此方法创建一个瞬态服务。一个新的每次请求瞬态服务时都会创建它的实例。

范围定义-此方法创建一个Scoped服务。一个新的作用域内的每个请求都会创建一次作用域服务。对于例如,在Web应用程序中,它为每个超文本传输协议创建1个实例请求,但在同一调用中的其他调用中使用相同的实例网络请求。

用哪一个

瞬态

  • 因为它们是在每次使用更多的内存和资源时创建的,并且可以对性能产生影响
  • 将此用于轻量级服务,其中包含很少或没有国家

范围

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

Singleton

  • 这些服务中的内存泄漏会随着时间的推移而增加。
  • 内存效率也很高,因为它们一旦创建就可以在任何地方重用。

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

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

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

  2. 永远不要将瞬态服务注入到作用域服务中(这会将瞬态服务转换为作用域。)

这幅图很好地说明了这个概念。不幸的是,我找不到这个图像的来源,但是有人制作了它,他以图像的形式很好地展示了这个概念。输入图片描述

更新:图片参考:ASP.NET核心服务寿命(信息图),作者:@刘文杰

DI容器一开始可能非常神秘,尤其是在生命周期方面。毕竟,容器使用反射使一切“正常工作”。思考容器实际上在幕后为你完成了什么:编写对象图是有帮助的。

对于. NET Web应用程序,使用DI容器的替代方法是将默认控制器激活器替换为您自己的,它必须手动管理生命周期并构建依赖关系图。出于学习目的,假设您有一个控制器激活器,该控制器激活器是硬编码的,每次有Web请求时都会返回一个特定控制器:

// This class is created once per application during startup.  In DI terms, it is the// "composition root."public class DumbControllerActivator{// Shared among all consumers from all requestsprivate static readonly Singleton1 singleton1 = new Singleton1();private static readonly Singleton2 singleton2 = new Singleton2();
// This method's responsibility is to construct a FooController and its dependecies.public FooController HandleFooRequest(){// Shared among all consumers in this requestvar scoped1 = new Scoped1();var scoped2 = new Scoped2(singleton1, scoped1);
return new FooController(singleton1,scoped1,new Transient1(                     // Fresh instancesingleton2,new Transient2(scoped2)),       // Fresh instancenew Transient3(                     // Fresh instancesingleton1,scoped1,new Transient1(                 // Fresh instancesingleton2,new Transient2(scoped2)));  // Fresh instance}}
  • 激活器只创建每个单例实例一次,然后在应用程序的整个生命周期内保留它。每个消费者共享该单个实例(甚至来自不同请求的消费者)。
  • 对于作用域依赖项,激活器为每个Web请求创建一个实例。在该请求中,每个消费者共享该单个实例,但从请求到请求,实例是不同的。
  • 对于临时依赖,每个消费者都有自己的私有实例。根本没有共享。

为了更深入地了解DI,我强烈推荐依赖注入原则、实践和模式这本书。我的回答基本上只是重复我在那里学到的东西。

可能生命周期的最佳例证是通过DbContext使用EntityFramework/Core。

建议DbContext和与DbContext交互的存储库应该使用Scoped生命周期连接,因为DbContext显然是一个有状态构造。所以你不想使用Singleton,因为你最终会遇到各种并发问题。你不想使用瞬态,因为DbContext不是线程安全的。请记住,瞬态适用于你处理无状态对象/类的用例。

由于大多数存储库都是由控制器调用的,因此使用Scoped生命周期确实是有意义的。可以想象,在单个操作方法期间,作为事务的一部分,可以多次调用DbContext。

本文没有直接讨论这些生存期,但对为什么范围生存期最适合DbContext给出了很好的解释。

https://mehdi.me/ambient-dbcontext-in-ef6/?msclkid=00251b05d01411ec8d85d232374f26d5