我应该如何在 MVC Core 中管理 DbContext Lifetime? [英] How should I manage DbContext Lifetime in MVC Core?

查看:24
本文介绍了我应该如何在 MVC Core 中管理 DbContext Lifetime?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

来自文档

<块引用>

实体框架上下文应该添加到服务容器中使用 Scoped 生命周期.这会自动处理,如果您使用如上所示的辅助方法.将使用的存储库Entity Framework 应该使用相同的生命周期.

我一直认为,我应该为我必须处理的每个工作单元创建一个新的 Context.这让我想到,如果我有一个 ServiceAServiceB,它们在 DbContext 上应用不同的操作,它们应该获得不同的实例DbContext.

文档内容如下:><块引用>

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

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

回到ServiceAServiceB,在我看来,Transient更合适.

我研究过,每个 HttpRequest Context 应该只保存一次,但我真的不明白这是如何工作的.

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

using (var transaction = dbContext.Database.BeginTransaction()){//创建一些实体var someEntity = new SomeEntity();dbContext.SomeEntity.Add(someEntity);//保存以获取实体的iddbContext.SaveChanges();//创建相关实体var relatedEntity = 新的相关实体{SomeEntityId = someEntity.Id};dbContext.RelatedEntity.Add(relatedEntity)dbContext.SaveChanges();交易.提交();}

这里我们需要保存上下文以获取与我们刚刚创建的另一个实体相关的实体的 ID.

同时另一个服务可以更新相同的上下文.据我所知,DbContext 不是线程安全的.

在这种情况下我应该使用 Transient 吗?为什么文档建议,我应该使用 Scoped?

我是否遗漏了框架的某些重要部分?

解决方案

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

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

相反,您应该为您创建的每个线程显式打开一个新的依赖范围.您可以通过注入 IServiceScopeFactory 并使用 CreateScope.结果对象将包含一个服务提供者,您可以从中检索您的依赖项.由于这是一个单独的作用域,因此将在此作用域的生命周期内重新创建像数据库上下文这样的作用域依赖项.

为了避免陷入服务定位器模式,您应该考虑让您的线程执行一个中央服务,它将所有必要的依赖项汇集在一起​​.然后线程可以这样做:

using (var scope = _scopeFactory.CreateScope()){var service = scope.ServiceProvider.GetService();服务.运行();}

BackgroundThreadService 和它的所有依赖可以遵循通用的依赖注入方式接收依赖.

From the Documentation

Entity Framework contexts should be added to the services container using the Scoped lifetime. This is taken care of automatically if you use the helper methods as shown above. Repositories that will make use of Entity Framework should use the same lifetime.

I always thought, that I should create a new Context for every single unit of work I have to process. This let me think, if I have a ServiceA and ServiceB, which are applying different actions on the DbContext that they should get a different Instance of DbContext.

The documentation reads as following:

  • Transient objects are always different; a new instance is provided to every controller and every service.

  • Scoped objects are the same within a request, but different across different request

Going back to ServiceA and ServiceB, it sounds to me, Transient is more suitable.

I have researched, that the Context should only saved once per HttpRequest, but I really do not understand how this does work.

Especially if we take a look at one Service:

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

Here we need to Save the context in order to get the ID of an Entity which is related to another one we just have created.

At the same time another service could update the same context. From what I have read, DbContext is not thread safe.

Should I use Transient in this case? Why does the documentation suggest, I should use Scoped?

Do I miss some important part of the framework?

解决方案

As others already explained, you should use a scoped dependency for database contexts to make sure it will be properly reused. For concurrency, remember that you can query the database asynchronously too, so you might not need actual threads.

If you do need threads, i.e. background workers, then it’s likely that those will have a different lifetime than the request. As such, those threads should not use dependencies retrieved from the request scope. When the request ends and its dependency scope is being closed, disposable dependencies will be properly disposed. For other threads, this would mean that their dependencies might end up getting disposed although they still need them: Bad idea.

Instead, you should explicitly open a new dependency scope for every thread you create. You can do that by injecting the IServiceScopeFactory and creating a scope using CreateScope. The resulting object will then contain a service provider which you can retrieve your dependencies from. Since this is a seperate scope, scoped dependencies like database contexts will be recreated for the lifetime of this scope.

In order to avoid getting into the service locator pattern, you should consider having one central service your thread executes that brings together all the necessary dependencies. The thread could then do this:

using (var scope = _scopeFactory.CreateScope())
{
    var service = scope.ServiceProvider.GetService<BackgroundThreadService>();
    service.Run();
}

The BackgroundThreadService and all its dependency can then follow the common dependency injection way of receiving dependencies.

这篇关于我应该如何在 MVC Core 中管理 DbContext Lifetime?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆