每个Web请求一个DbContext ...为什么?

本文翻译自:One DbContext per web request… why?

I have been reading a lot of articles explaining how to set up Entity Framework's DbContext so that only one is created and used per HTTP web request using various DI frameworks. 我阅读了许多文章,这些文章解释了如何设置实体框架的DbContext以便使用各种DI框架为每个HTTP Web请求创建和使用一个实体。

Why is this a good idea in the first place? 为什么这首先是个好主意? What advantages do you gain by using this approach? 通过使用这种方法,您可以获得什么优势? Are there certain situations where this would be a good idea? 在某些情况下这是个好主意吗? Are there things that you can do using this technique that you can't do when instantiating DbContext s per repository method call? 使用每个存储库方法实例化DbContext时,您是否可以使用这种技术来做某些事情?


#1楼

参考:https://stackoom.com/question/iPlW/每个Web请求一个DbContext-为什么


#2楼

What I like about it is that it aligns the unit-of-work (as the user sees it - ie a page submit) with the unit-of-work in the ORM sense. 我喜欢它的原因是它使工作单位(如用户所见-即页面提交)与ORM意义上的工作单位对齐。

Therefore, you can make the entire page submission transactional, which you could not do if you were exposing CRUD methods with each creating a new context. 因此,您可以使整个页面提交都具有事务性,而如果要在每个CRUD方法都创建一个新上下文的情况下公开它,则无法做到这一点。


#3楼

I'm pretty certain it is because the DbContext is not at all thread safe. 我可以肯定这是因为DbContext根本不是线程安全的。 So sharing the thing is never a good idea. 因此,共享事物绝不是一个好主意。


#4楼

NOTE: This answer talks about the Entity Framework's DbContext , but it is applicable to any sort of Unit of Work implementation, such as LINQ to SQL's DataContext , and NHibernate's ISession . 注意:该答案讨论了实体框架的DbContext ,但是它适用于任何类型的工作单元实现,例如LINQ to SQL的DataContext和NHibernate的ISession

Let start by echoing Ian: Having a single DbContext for the whole application is a Bad Idea. 让我们从呼应伊恩开始:对于整个应用程序只有一个DbContext是一个坏主意。 The only situation where this makes sense is when you have a single-threaded application and a database that is solely used by that single application instance. 唯一有意义的情况是当您拥有单线程应用程序和该单个应用程序实例专用的数据库时。 The DbContext is not thread-safe and and since the DbContext caches data, it gets stale pretty soon. DbContext不是线程安全的,并且由于DbContext缓存数据,因此很快就会过时。 This will get you in all sorts of trouble when multiple users/applications work on that database simultaneously (which is very common of course). 当多个用户/应用程序同时在该数据库上工作时,这将给您带来各种麻烦(这是很常见的)。 But I expect you already know that and just want to know why not to just inject a new instance (ie with a transient lifestyle) of the DbContext into anyone who needs it. 但是我希望您已经知道这一点,并且只想知道为什么不将DbContext的新实例(即具有短暂生活方式)注入需要的任何人。 (for more information about why a single DbContext -or even on context per thread- is bad, read this answer ). (有关为何单个DbContext或什至每个线程的上下文不好的更多信息,请阅读此答案 )。

Let me start by saying that registering a DbContext as transient could work, but typically you want to have a single instance of such a unit of work within a certain scope. 让我首先说一下,将DbContext注册为瞬态是可行的,但是通常您希望在一定范围内拥有此类工作单元的单个实例。 In a web application, it can be practical to define such a scope on the boundaries of a web request; 在Web应用程序中,在Web请求的边界上定义这样的范围可能是实用的。 thus a Per Web Request lifestyle. 因此,按网络请求的生活方式。 This allows you to let a whole set of objects operate within the same context. 这使您可以让整套对象在同一上下文中操作。 In other words, they operate within the same business transaction. 换句话说,它们在同一业务交易中运作。

If you have no goal of having a set of operations operate inside the same context, in that case the transient lifestyle is fine, but there are a few things to watch: 如果您没有目标要在同一上下文中进行一组操作,那么在这种情况下,短暂的生活方式就可以了,但是需要注意以下几点:

  • Since every object gets its own instance, every class that changes the state of the system, needs to call _context.SaveChanges() (otherwise changes would get lost). 由于每个对象都有其自己的实例,因此更改系统状态的每个类都需要调用_context.SaveChanges() (否则更改将丢失)。 This can complicate your code, and adds a second responsibility to the code (the responsibility of controlling the context), and is a violation of the Single Responsibility Principle . 这可能会使您的代码复杂化,并给代码增加第二个责任(控制上下文的责任),并且违反了“ 单一责任原则”
  • You need to make sure that entities [loaded and saved by a DbContext ] never leave the scope of such a class, because they can't be used in the context instance of another class. 您需要确保[由DbContext加载和保存的]实体永远不会离开此类的范围,因为它们不能在另一个类的上下文实例中使用。 This can complicate your code enormously, because when you need those entities, you need to load them again by id, which could also cause performance problems. 这会使您的代码变得非常复杂,因为当您需要这些实体时,需要通过id重新加载它们,这也可能导致性能问题。
  • Since DbContext implements IDisposable , you probably still want to Dispose all created instances. 由于DbContext实现了IDisposable ,因此您可能仍想处置所有创建的实例。 If you want to do this, you basically have two options. 如果要执行此操作,则基本上有两个选择。 You need to dispose them in the same method right after calling context.SaveChanges() , but in that case the business logic takes ownership of an object it gets passed on from the outside. 您需要在调用context.SaveChanges()之后立即以相同的方法处理它们,但是在这种情况下,业务逻辑将获取对象的所有权,该对象将从外部传递给对象。 The second option is to Dispose all created instances on the boundary of the Http Request, but in that case you still need some sort of scoping to let the container know when those instances need to be Disposed. 第二种选择是将所有创建的实例放置在Http请求的边界上,但是在那种情况下,您仍然需要某种范围设定,以使容器知道何时需要丢弃这些实例。

Another option is to not inject a DbContext at all. 另一种选择是根本不注入DbContext Instead, you inject a DbContextFactory that is able to create a new instance (I used to use this approach in the past). 相反,您注入了一个能够创建新实例的DbContextFactory (我过去曾经使用这种方法)。 This way the business logic controls the context explicitly. 这样,业务逻辑即可明确控制上下文。 If might look like this: 如果可能看起来像这样:

public void SomeOperation()
{
    using (var context = this.contextFactory.CreateNew())
    {
        var entities = this.otherDependency.Operate(
            context, "some value");

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}

The plus side of this is that you manage the life of the DbContext explicitly and it is easy to set this up. DbContext一面是,您可以显式管理DbContext的生命,并且很容易进行设置。 It also allows you to use a single context in a certain scope, which has clear advantages, such as running code in a single business transaction, and being able to pass around entities, since they originate from the same DbContext . 它还允许您在一定范围内使用单个上下文,这具有明显的优势,例如在单个业务事务中运行代码,并且能够传递实体,因为它们源自同一DbContext

The downside is that you will have to pass around the DbContext from method to method (which is termed Method Injection). 缺点是您将必须在方法之间传递DbContext (称为方法注入)。 Note that in a sense this solution is the same as the 'scoped' approach, but now the scope is controlled in the application code itself (and is possibly repeated many times). 请注意,从某种意义上说,此解决方案与“作用域”方法相同,但是现在范围是在应用程序代码本身中控制的(并且可能会重复很多次)。 It is the application that is responsible for creating and disposing the unit of work. 负责创建和处理工作单元的是应用程序。 Since the DbContext is created after the dependency graph is constructed, Constructor Injection is out of the picture and you need to defer to Method Injection when you need to pass on the context from one class to the other. 由于DbContext是在构造依赖关系图之后创建的,因此构造函数注入不在图片之内,并且当您需要将上下文从一个类传递到另一个类时,您需要DbContext方法注入。

Method Injection isn't that bad, but when the business logic gets more complex, and more classes get involved, you will have to pass it from method to method and class to class, which can complicate the code a lot (I've seen this in the past). 方法注入并没有那么糟糕,但是当业务逻辑变得更加复杂,并且涉及到更多的类时,您将不得不将其从方法传递到方法,并将类传递给类,这会使代码复杂化很多(我已经看到了)过去)。 For a simple application, this approach will do just fine though. 对于简单的应用程序,这种方法虽然可以。

Because of the downsides, this factory approach has for bigger systems, another approach can be useful and that is the one where you let the container or the infrastructure code / Composition Root manage the unit of work. 由于不利因素,这种工厂方法适用于较大的系统,另一种方法可能有用,那就是让容器或基础结构代码/ 组合根管理工作单元的方法。 This is the style that your question is about. 这是您的问题涉及的样式。

By letting the container and/or the infrastructure handle this, your application code is not polluted by having to create, (optionally) commit and Dispose a UoW instance, which keeps the business logic simple and clean (just a Single Responsibility). 通过让容器和/或基础结构来处理此问题,您的应用程序代码不会因必须创建,(可选)提交和处置UoW实例而受到污染,这使业务逻辑变得简单,整洁(仅是单一职责)。 There are some difficulties with this approach. 这种方法存在一些困难。 For instance, were do you Commit and Dispose the instance? 例如,您是否提交并处置该实例?

Disposing a unit of work can be done at the end of the web request. 可以在Web请求结束时完成工作单元的布置。 Many people however, incorrectly assume that this is also the place to Commit the unit of work. 但是,许多人错误地认为这也是提交工作单元的地方。 However, at that point in the application, you simply can't determine for sure that the unit of work should actually be committed. 但是,在应用程序中的那一点上,您根本无法确定应确实落实工作单元。 eg If the business layer code threw an exception that was caught higher up the callstack, you definitely don't want to Commit. 例如,如果业务层的代码扔的被抓越往上调用堆栈异常,你绝对希望提交。

The real solution is again to explicitly manage some sort of scope, but this time do it inside the Composition Root. 真正的解决方案是再次明确管理某种范围,但是这次在“合成根”内部执行。 Abstracting all business logic behind the command / handler pattern , you will be able to write a decorator that can be wrapped around each command handler that allows to do this. 命令/处理程序模式之后抽象所有业务逻辑,您将能够编写一个装饰器,该装饰器可以包装在允许执行此操作的每个命令处理器周围。 Example: 例:

class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    readonly DbContext context;
    readonly ICommandHandler<TCommand> decorated;

    public TransactionCommandHandlerDecorator(
        DbContext context,
        ICommandHandler<TCommand> decorated)
    {
        this.context = context;
        this.decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        context.SaveChanges();
    } 
}

This ensures that you only need to write this infrastructure code once. 这样可以确保您只需要编写一次此基础结构代码。 Any solid DI container allows you to configure such a decorator to be wrapped around all ICommandHandler<T> implementations in a consistent manner. 任何固体DI容器都允许您配置这样的装饰器,使其以一致的方式包装在所有ICommandHandler<T>实现中。


#5楼

I agree with previous opinions. 我同意以前的意见。 It is good to say, that if you are going to share DbContext in single thread app, you'll need more memory. 可以说,如果要在单线程应用程序中共享DbContext,则需要更多内存。 For example my web application on Azure (one extra small instance) needs another 150 MB of memory and I have about 30 users per hour. 例如,我在Azure上的Web应用程序(一个额外的小型实例)需要另外150 MB的内存,而我每小时大约有30个用户。 应用程序在HTTP请求中共享DBContext

Here is real example image: application have been deployed in 12PM 这是真实的示例图像:应用程序已在12PM部署


#6楼

Another understated reason for not using a singleton DbContext, even in a single threaded single user application, is because of the identity map pattern it uses. 即使在单线程单用户应用程序中也不使用单例DbContext的另一个低估原因是由于它使用的身份映射模式。 It means that every time you retrieve data using query or by id, it will keep the retrieved entity instances in cache. 这意味着每次使用查询或按ID检索数据时,它将检索到的实体实例保存在缓存中。 The next time you retrieve the same entity, it will give you the cached instance of the entity, if available, with any modifications you have done in the same session. 下次您检索同一实体时,它将为您提供该实体的缓存实例(如果有),并具有您在同一会话中所做的任何修改。 This is necessary so the SaveChanges method does not end up with multiple different entity instances of the same database record(s); 这是必需的,因此SaveChanges方法不会以同一数据库记录的多个不同实体实例结尾。 otherwise, the context would have to somehow merge the data from all those entity instances. 否则,上下文将必须以某种方式合并所有那些实体实例中的数据。

The reason that is a problem is a singleton DbContext can become a time bomb that could eventually cache the whole database + the overhead of .NET objects in memory. 问题的原因是单例DbContext可能成为定时炸弹,最终可能会缓存整个数据库+ .NET对象在内存中的开销。

There are ways around this behavior by only using Linq queries with the .NoTracking() extension method. 通过仅将Linq查询与.NoTracking()扩展方法一起使用,可以解决此问题。 Also these days PCs have a lot of RAM. 同样,这些天PC具有大量RAM。 But usually that is not the desired behavior. 但是通常这不是期望的行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值