One DbContext per web request… why?
我已经阅读了很多文章,解释了如何设置实体框架的
为什么一开始这是个好主意?使用这种方法有什么好处?在某些情况下,这是个好主意吗?在实例化每个存储库方法调用的
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'sDataContext , and NHibernate'sISession .Ok.
让我们从回应伊恩开始:为整个应用程序使用单个
首先,我要说,将
如果你没有在相同的环境下进行一系列操作的目标,那么在这种情况下,短暂的生活方式是可以的,但是有一些事情需要注意:好的。
- 因为每个对象都有自己的实例,所以更改系统状态的每个类都需要调用
_context.SaveChanges() ,否则更改将丢失。这会使代码复杂化,并向代码添加第二个责任(控制上下文的责任),这违反了单一责任原则。 - 您需要确保[由
DbContext 加载和保存的实体]永远不会离开此类的范围,因为它们不能在另一个类的上下文实例中使用。这会使您的代码非常复杂,因为当您需要这些实体时,您需要按ID再次加载它们,这也可能导致性能问题。 - 由于
DbContext 实现了IDisposable ,您可能仍然想要处理所有创建的实例。如果你想这样做,你基本上有两个选择。在调用context.SaveChanges() 之后,您需要使用相同的方法来处理它们,但是在这种情况下,业务逻辑会取得从外部传递给它的对象的所有权。第二个选项是释放HTTP请求边界上创建的所有实例,但在这种情况下,您仍然需要某种范围来让容器知道何时需要释放这些实例。
另一种选择是根本不注入
1 2 3 4 5 6 7 8 9 10 11 12 | public void SomeOperation() { using (var context = this.contextFactory.CreateNew()) { var entities = this.otherDependency.Operate( context,"some value"); context.Entities.InsertOnSubmit(entities); context.SaveChanges(); } } |
这样做的好处是,您可以明确地管理
缺点是您必须将
方法注入并没有那么糟糕,但是当业务逻辑变得更复杂,并且涉及到更多的类时,您必须将它从一个方法传递到另一个方法,并将它从一个类传递到另一个类,这会使代码变得非常复杂(我以前见过这种情况)。对于一个简单的应用程序,这种方法可以做得很好。好的。
由于缺点,这种工厂方法对于更大的系统是有用的,另一种方法是让容器或基础结构代码/组合根管理工作单元的方法。这就是你问题的风格。好的。
通过让容器和/或基础结构处理这一点,您的应用程序代码不会因为必须创建(可选)提交和处理UOW实例而受到污染,这样可以保持业务逻辑的简单和干净(只是一个职责)。这种方法有一些困难。例如,您是否提交并释放了该实例?好的。
处理工作单元可以在Web请求结束时完成。然而,许多人错误地认为这也是提交工作单元的地方。然而,在应用程序中的这一点上,您只是不能确定工作单元是否应该被提交。例如,如果业务层代码抛出了一个异常,而该异常在调用堆栈中被捕获得更高,那么您肯定不想提交。好的。
真正的解决方案是再次显式地管理某种范围,但这次在复合根目录中进行。抽象命令/处理程序模式背后的所有业务逻辑后,您将能够编写一个修饰器,它可以包装在每个允许这样做的命令处理程序周围。例子:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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(); } } |
这确保您只需要编写一次这个基础结构代码。任何实体DI容器都允许您将这样一个装饰器配置为以一致的方式包装在所有
这里没有一个答案能真正回答这个问题。OP没有询问单实例/每个应用程序的dbContext设计,他询问了每个(web)请求设计,以及可能存在的好处。
我将引用http://mehdi.me/ambient-dbcontext-in-ef6/,因为mehdi是一个很棒的资源:
Possible performance gains.
Each DbContext instance maintains a first-level cache of all the entities its loads from the database. Whenever you query an entity by its primary key, the DbContext will first attempt to retrieve it from its first-level cache before defaulting to querying it from the database. Depending on your data query pattern, re-using the same DbContext across multiple sequential business transactions may result in a fewer database queries being made thanks to the DbContext first-level cache.
It enables lazy-loading.
If your services return persistent entities (as opposed to returning view models or other sorts of DTOs) and you'd like to take advantage of lazy-loading on those entities, the lifetime of the DbContext instance from which those entities were retrieved must extend beyond the scope of the business transaction. If the service method disposed the DbContext instance it used before returning, any attempt to lazy-load properties on the returned entities would fail (whether or not using lazy-loading is a good idea is a different debate altogether which we won't get into here). In our web application example, lazy-loading would typically be used in controller action methods on entities returned by a separate service layer. In that case, the DbContext instance that was used by the service method to load these entities would need to remain alive for the duration of the web request (or at the very least until the action method has completed).
记住也有缺点。这个链接包含许多其他关于这个主题的阅读资源。
如果有人偶然发现了这个问题,而没有全神贯注于那些没有真正解决这个问题的答案,就把它贴出来。
微软提出了两个相互矛盾的建议,许多人使用dbContexts的方式完全不同。
这些内容相互矛盾,因为如果您的请求做了很多与数据库无关的事情,那么您的dbContext将毫无理由地保留。因此,当您的请求只是等待随机的事情完成时,让dbContext保持活动状态是浪费的…
很多遵循规则1的人都将dbContexts放在"存储库模式"中,并为每个数据库查询创建一个新实例,因此每个请求都有x*dbContext
他们只需获取数据并尽快处理上下文。许多人认为这是一种可以接受的做法。虽然这样做的好处是占用您的数据库资源的时间最短,但它显然牺牲了所有UnitOfWork和缓存Candy EF必须提供的功能。
保持dbContext的单个多用途实例的活动状态可以最大限度地提高缓存的好处,但由于dbContext不是线程安全的,并且每个Web请求都在其自己的线程上运行,因此每个请求的dbContext是可以保持它的最长时间。
因此,EF的团队建议每个请求使用1 db上下文,这显然是基于这样一个事实:在一个Web应用程序中,一个工作单元很可能在一个请求中,而该请求只有一个线程。因此,每个请求一个dbContext类似于UnitOfWork和缓存的理想好处。
但在许多情况下,这是不正确的。我考虑将一个单独的工作单元记录下来,因此在异步线程中为请求后记录提供一个新的dbContext是完全可以接受的。
最后,我们发现dbContext的生存期仅限于这两个参数。工作单元和螺纹
我很确定这是因为dbContext根本不安全。所以分享这件事从来都不是一个好主意。
问题或讨论中没有真正解决的一件事是dbContext无法取消更改。您可以提交更改,但不能清除更改树,因此,如果您使用每个请求上下文,那么无论出于什么原因需要放弃更改,您都会走运。
我个人在需要时创建dbContext实例-通常附加到业务组件,这些组件能够在需要时重新创建上下文。这样我就可以控制这个过程,而不是让一个实例强迫我。我也不必在每次控制器启动时创建dbContext,不管它是否被实际使用。然后,如果我仍然想要每个请求实例,我可以在ctor中创建它们(通过DI或手动),或者在每个控制器方法中根据需要创建它们。我个人通常采用后一种方法,以避免在实际不需要时创建dbContext实例。
这也取决于你从哪个角度看。对我来说,每个请求实例都没有意义。dbContext真的属于HTTP请求吗?就行为而言,那是错误的地方。您的业务组件应该创建您的上下文,而不是HTTP请求。然后,您可以根据需要创建或丢弃业务组件,而不必担心上下文的生命周期。
我同意以前的意见。很好地说,如果要在单线程应用程序中共享dbContext,则需要更多内存。例如,我在Azure上的Web应用程序(一个非常小的实例)需要另一个150MB的内存,我每小时有大约30个用户。
下面是真实的示例图片:应用程序已在下午12点部署
我喜欢的是它将工作单元(如用户所见,即页面提交)与ORM意义上的工作单元对齐。
因此,可以使整个页面提交成为事务性的,如果在每次创建新上下文时都公开CRUD方法,则不能这样做。
另一个不使用singleton dbcontext(即使在单线程单用户应用程序中)的原因是它使用的标识映射模式。这意味着每次使用查询或按ID检索数据时,它都会将检索到的实体实例保存在缓存中。下次检索同一个实体时,它将为您提供该实体的缓存实例(如果可用),以及您在同一会话中所做的任何修改。这是必需的,因此savechanges方法不会以同一数据库记录的多个不同实体实例结束;否则,上下文将不得不以某种方式合并来自所有这些实体实例的数据。
出现问题的原因是,单个dbContext可能成为一个定时炸弹,最终可能会缓存整个数据库+内存中.NET对象的开销。
有一些方法可以避免这种行为,只使用带有
另一个需要特别注意的关于实体框架的问题是,使用创建新实体、延迟加载以及使用这些新实体(来自同一上下文)的组合时。如果不使用idbset.create(vs只是new),则当从创建实体的上下文中检索到该实体时,该实体上的延迟加载将无法工作。例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class Foo { public string Id {get; set; } public string BarId {get; set; } // lazy loaded relationship to bar public virtual Bar Bar { get; set;} } var foo = new Foo { Id ="foo id" BarId ="some existing bar id" }; dbContext.Set<Foo>().Add(foo); dbContext.SaveChanges(); // some other code, using the same context var foo = dbContext.Set<Foo>().Find("foo id"); var barProp = foo.Bar.SomeBarProp; // fails with null reference even though we have BarId set. |