.NET Core/EF 6 - 依赖注入范围

14

我目前正在使用EF 6设置.NET Core应用程序,并且在理解各种依赖项注册方法的适当用法方面遇到了一些问题。据我所知:

  • Transient:需要时创建对象(即每次请求时都创建新实例)
  • Singleton:应用程序启动时创建单个实例,并可用于所有后续请求
  • Scoped:在请求期间可用

在我的情况下,我设置了一对基于CQRS模式的DbContext来处理数据库查询/命令,并将其注册为Scoped

services.AddScoped((_) => new TestCommandContext(Configuration["Data:TestConnection:ConnectionString"]));
services.AddScoped((_) => new TestQueryContext(Configuration["Data:TestConnection:ConnectionString"]));

根据 ASP.NET Getting Started with ASP.NET 5 and Entity Framework 6 文档的说明:

为确保性能和 Entity Framework 的可靠操作,每个作用域应仅解析一次上下文。

接着我注册了相应的 UOW 类:

services.AddTransient<ITestCommandUnit, TestCommandUnit>();
services.AddTransient<ITestQueryUnit, TestQueryUnit>();

根据这篇文章,我在这里使用Transient。该文章建议:

使用Transient作用域注册的服务会在应用程序中需要时创建。这意味着依赖注入框架将在每次执行方法(其中创建依赖项)时创建(已注册服务)类的新实例。

基于这一理解,我也在将我的存储库和服务类注册为Scoped

services.AddScoped<ITestCommandRepository, TestCommandRepository>();
services.AddScoped<ITestQueryRepository, TestQueryRepository>();

services.AddScoped<ITestCommandService, TestCommandService>();
services.AddScoped<ITestQueryService, TestQueryService>();

然后按需在我的控制器中调用我的相应服务层方法:

public class TestController : BaseController
{
    private ITestQueryService testQueryService;

    // Get new object of type TestQueryService via DI
    public TestController(ITestQueryService testQueryService)
    {
        this.testQueryService = testQueryService;
    }

    [HttpGet]
    public IActionResult Edit(int id)
    {
        if (id > 0)
        {
            EditViewModel viewModel = new EditViewModel();
            viewModel.TestObject = testQueryService.GetById(id);
            return View(viewModel);
        }
        else
        {
            return RedirectToAction("Error", new { errorMessage = "No object with the specified Id could be found." });
        }
    }
}

测试中,这个配置似乎在工作,将DbContext设置为 Scoped 是有道理的 - 每次请求时创建一个新的上下文对象似乎是不必要/低效的。

然而,我不确定其他对象选择Transient/Singleton/Scoped 的最佳配置方案。能否有人帮助我了解此特定设计模式的最佳配置方案?

上述设置正在工作中,但我正在寻找更多了解为什么应该使用我选择的作用域(Scope)。例如,我的 UOW 类应该使用 Transient 是最佳选择吗?在这种情况下,为什么Singleton 不是更好的选择?等等。


“选择...对于其他对象而言,我不知道该怎么做。” 看起来你已经做出了选择,并且它正在运作。你需要帮助的是哪些其他对象? - Marc L.
虽然我的作用域选择确实“有效”,但我希望有人能够详细说明在上述配置的背景下,每个作用域选项的优缺点。我已经让它工作了,但我正在寻求更多的理解。 - Ben Walters
绝对不是单例模式。如果您注册了依赖于作用域注册类型的单例,可能会遇到几个问题。您可以查看此链接:https://blog.markvincze.com/two-gotchas-with-scoped-and-singleton-dependencies-in-asp-net-core/。根据您的问题,似乎您的选择是可以的。但是请注意,官方文档关于Transient的说明如下:每次请求时都会创建瞬态生命周期服务。这种生命周期最适合轻量级、无状态的服务。 - jpgrassi
2个回答

10

一般来说,我的经验法则是:

  1. Scoped - 是首选,可以节省缓存和您的时间,因为状态在整个请求中共享。 没有并发问题(所有作用域服务共享单个线程)。 如果不知道如何注册类,则选择范围限定。 通常需要在单个请求中多次使用某些内容-可以计算一次,并将值设置在字段中,因此下一个查询到客户的CreditLimit将不会命中数据存储。

  2. Singleton 对于缓存(服务器范围内),配置类,针对多个线程设计的对象(多种请求)非常适用。请注意,单例不应依赖于作用域对象。同时要注意在多个线程中调用单例。如果需要单例处理请求数据,将其作为函数参数传递。

  3. 我应用程序中很少使用临时注册。 我将其用于具有内部状态并可以多次使用且不应共享该状态的类。通常是工具或框架类。

例如scoped类?SqlConnection - 您不希望从单个请求中打开多个与db的连接(因为它由连接池处理)。 使用该连接的服务(服务只做一件事,因此不需要多个实例)。 Asp控制器。

示例单例模式?今天最受欢迎的文章。Zip-Code验证器(没有依赖项,可以是单例模式)。

示例瞬态?想一想,如果所有请求中的列表共享状态会发生什么。列表不为请求提供服务,而是为您的代码提供服务,并且可能在单个请求期间用于不同的目的。

请记住,如果单例具有瞬态或作用域依赖项,则在单例被处理(应用程序重新加载)之前它将不被处理。因此,范围内的事物可以依赖于单例,但单例不能依赖于范围。

谈到CQRS和DbContext - 在我的应用程序中,我有一个单一的DbContext,由Commands和Queries共享。所有内容都注册在生命周期范围内(命令或查询完成后没有保留状态,因此可以重复使用。将其设置为瞬态也可以运行)。 另一个示例是用于html元素生成唯一id的类。它被注册为作用域,并在每次查询新id时递增内部计数器。如果该类是瞬态,则在下一个类调用时将失去其状态。

请注意,有些人有其他观点。如果您使用多个生命周期范围,则将依赖关系移至瞬态可能更好。如果需要多次使用单个依赖项,我喜欢传递工厂,并尽量在我的应用程序中只有一个生命周期范围。


-1
  • 瞬态对象始终不同;每个控制器和每个服务都提供一个新实例。
  • 作用域对象在请求内相同,但在不同请求之间不同
  • 单例对象对于每个对象和每个请求都是相同的(无论是否在ConfigureServices中提供实例)

在您的情况下,您注入的服务不依赖于同一请求中其他对象的状态。 您使用该服务获取 Db 中的一个对象。瞬态服务或作用域服务均可。

如果在同一请求中,您需要根据同一请求中的计算更改对象的状态,则需要使用从开始到结束都存在于同一请求中的对象(即:作用域对象)。


这在问题中已经提到,但并没有回答实际问题。 - Onots

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接