C# 我应该如何在MVC核心中管理DbContext生存期?

C# 我应该如何在MVC核心中管理DbContext生存期?,c#,asp.net-core,asp.net-core-mvc,.net-core,entity-framework-core,C#,Asp.net Core,Asp.net Core Mvc,.net Core,Entity Framework Core,从 实体框架上下文应添加到服务容器中 使用范围限定的生存期。如果您 使用如上所示的帮助器方法。将使用的存储库 实体框架的类型应使用相同的生存期 我一直认为,我应该为我必须处理的每一个工作单元创建一个新的上下文。这让我想到,如果我有一个ServiceA和ServiceB,它们在DbContext上应用不同的操作,那么它们应该得到一个不同的DbContext实例 决议案文如下: Transient对象总是不同的;为每个控制器和每个服务提供一个新实例 作用域对象在请求中相同,但在不同的请求中不同

实体框架上下文应添加到服务容器中 使用范围限定的
生存期。如果您
使用如上所示的帮助器方法。将使用的存储库
实体框架的类型应使用相同的生存期

我一直认为,我应该为我必须处理的每一个工作单元创建一个新的
上下文。这让我想到,如果我有一个
ServiceA
ServiceB
,它们在
DbContext
上应用不同的操作,那么它们应该得到一个不同的
DbContext
实例

决议案文如下:

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

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

回到
ServiceA
ServiceB
,我觉得,
Transient
更合适

我已经研究过,每个
HttpRequest
只能保存一次上下文,但我真的不明白这是如何工作的

特别是如果我们看一个服务:

using (var transaction = dbContext.Database.BeginTransaction())
{
    //Create some entity
    var someEntity = new SomeEntity();
    dbContext.SomeEntity.Add(someEntity);

    //Save in order to get the the id of the entity
    dbContext.SaveChanges();

    //Create related entity
    var relatedEntity = new RelatedEntity
    {
        SomeEntityId = someEntity.Id
    };
    dbContext.RelatedEntity.Add(relatedEntity)
    dbContext.SaveChanges();
    transaction.Commit();
}
在这里,我们需要保存上下文,以便获得一个实体的ID,该实体与我们刚刚创建的另一个实体相关

同时,另一个服务可以更新相同的上下文。从我读到的内容来看,
DbContext
不是线程安全的

在这种情况下,我应该使用
瞬态
?为什么文档建议我应该使用
范围


我是否错过了框架的一些重要部分?

我相信在大多数情况下,当您使用作用域生命周期时,您不会遇到并发问题。即使在您发布的示例中,也并没有并发问题,因为当前请求中的服务将随后被调用。我甚至无法想象在一个HTTP请求(作用域)的上下文中并行运行2个或多个服务(这是可能的,但并不常见)

生命周期只是存储数据的一种方式(这里很简单)。只要看看流行DI框架中的一些生命周期管理器,它们的工作原理都是一样的——这就是实现一次性模式的类似字典的对象。使用Transient,我相信您的get对象方法将始终返回null,因此DI将在每次请求时创建新实例。SingleInstance将对象存储在类似静态并发字典的东西中,所以容器将只创建一次实例,然后接收现有实例

Scoped通常指范围对象用于存储创建的对象。在asp网络管道中,它通常意味着与每个请求相同(因为范围可以通过管道传递)

简而言之,不用担心,只要使用作用域,它是安全的,您可以根据请求调用它


在我的解释中,我尽量做到非常简单,您可以随时查看源代码以查找您需要的匹配详细信息

正如其他人已经解释过的,您应该对数据库上下文使用作用域依赖项,以确保它将被正确重用。对于并发性,请记住也可以异步查询数据库,因此可能不需要实际的线程

如果您确实需要线程,即后台工作线程,那么这些线程的生存期可能与请求不同。因此,这些线程不应该使用从请求范围检索的依赖项。当请求结束并关闭其依赖范围时,将正确处理一次性依赖项。对于其他线程,这意味着它们的依赖关系可能最终会被处理掉,尽管它们仍然需要它们:这是个坏主意

相反,您应该为创建的每个线程显式地打开一个新的依赖范围。您可以通过注入并使用创建作用域来实现这一点。然后,生成的对象将包含一个服务提供者,您可以从中检索依赖项。由于这是一个单独的作用域,因此在该作用域的生存期内将重新创建作用域依赖项,如数据库上下文

<>为了避免进入服务定位器模式,您应该考虑线程执行的一个中心服务,它将所有必要的依赖项组合在一起。然后线程可以执行以下操作:

using (var scope = _scopeFactory.CreateScope())
{
    var service = scope.ServiceProvider.GetService<BackgroundThreadService>();
    service.Run();
}
使用(var scope=\u scopeFactory.CreateScope())
{
var service=scope.ServiceProvider.GetService();
service.Run();
}

BackgroundThreadService
及其所有依赖项都可以遵循接收依赖项的通用依赖项注入方式。

切勿使用瞬态
DbContext
注册。瞬时生存期将在每次被另一个服务请求时创建一个新的服务实例。这将导致每个请求有多个
DbContext
s。在同一请求中重用相同的
DbContext
实例没有问题:每个请求都绑定到一个线程,因此根本不存在线程安全问题。但是如果出于什么原因(例如Task.Start)并行处理服务,会发生什么情况@你说的平行是什么意思?是否显式生成一个使用相同的
DbContext
的新线程?是的,例如,如果我显式启动一个新线程,并希望注入DbContext。如果配置了
作用域
,我假设它是同一个DbContext实例解析的@FedericoDipumaI不明白并行执行I/O绑定操作(如db访问)的好处是什么,但如果您真的想继续走这条路,我认为您唯一的选择是为
DbContext
(类似
services.AddSingleton(s=>new Func()=>新建MyDbContext(/*..*/);
),并使用
usi自己管理它们的生命周期