每个Web请求一个DbContext ...为什么?
我一直在阅读大量文章,解释如何设置实体框架的DbContext
以便只使用各种DI框架为每个HTTP Web请求创建和使用一个。
为什么这首先是个好主意? 使用这种方法你有什么优势? 在某些情况下,这将是一个好主意吗? 有没有什么事情可以使用这种技术,当你在每个存储库方法调用中实例化DbContext
时都无法做到?
注意:这个答案是关于实体框架的DbContext
,但它适用于任何类型的工作单元实现,例如LINQ to SQL的DataContext
和NHibernate的ISession
。
首先回应Ian:为整个应用程序提供单个DbContext
是一个糟糕的想法。 唯一有意义的情况是当你有一个单线程应用程序和一个单独的应用程序实例使用的数据库时。 DbContext
不是线程安全的,并且自DbContext
缓存数据以来,它很快就会变陈旧。 当多个用户/应用程序同时在该数据库上工作时(这当然很常见),这会让您遇到各种麻烦。 但我希望你已经知道这一点,只是想知道为什么不将DbContext
一个新实例(即暂时的生活方式)注入任何需要它的人。 (有关为什么单个DbContext
更多信息 - 或者甚至在每个线程的上下文中都是坏的,请阅读此答案)。
让我开始说,注册一个DbContext
作为瞬态可以工作,但通常你想在一定范围内有一个这样的工作单元的单个实例。 在Web应用程序中,在Web请求的边界上定义这样的范围是可行的; 因此是Per Web Request生活方式。 这允许您让一组对象在相同的上下文中操作。 换句话说,他们在相同的商业交易中运作。
如果你没有在同一个环境下进行一系列操作的目标,在这种情况下,暂时的生活方式很好,但有几件事值得关注:
_context.SaveChanges()
(否则更改会丢失)。 这会使代码复杂化,并增加了代码的第二个责任(控制上下文的责任),这违反了单一责任原则。 DbContext
加载和保存]的实体永远不会离开这样的类的范围,因为它们不能用于其他类的上下文实例。 这会使你的代码变得非常复杂,因为当你需要这些实体时,你需要通过id再次加载它们,这也会导致性能问题。 DbContext
实现了IDisposable
,你可能仍然想要处理所有创建的实例。 如果你想这样做,你基本上有两种选择。 您需要在调用context.SaveChanges()
之后立即将它们置于相同的方法中,但在这种情况下,业务逻辑将从外部获取传递的对象的所有权。 第二种选择是将所有创建的实例配置到Http Request的边界上,但在这种情况下,您仍然需要某种方法范围来让容器知道何时需要Dispose。 另一种选择是根本不注入DbContext
。 相反,你注入一个能够创建一个新实例的DbContextFactory
(我过去曾经使用这种方法)。 这样业务逻辑显式控制上下文。 如果可能看起来像这样:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
这样做的DbContext
在于,您可以明确地管理DbContext
的生命,并且很容易对其进行设置。 它还允许您在特定范围内使用单个上下文,这具有明显的优势,例如在单个业务事务中运行代码,并且能够传递实体,因为它们源自相同的DbContext
。
缺点是你必须传递DbContext
从方法到方法(这被称为方法注入)。 请注意,从某种意义上说,这种解决方案与“范围”方法相同,但现在范围在应用程序代码本身中进行控制(并且可能重复多次)。 这是负责创建和处理工作单元的应用程序。 由于DbContext
是在构建依赖关系图之后创建的,所以构造函数注入不在图中,当需要将上下文从一个类传递到另一个类时,您需要DbContext
方法注入。
方法注入并没有那么糟糕,但是当业务逻辑变得更加复杂,并且涉及更多的类时,您必须将其从方法传递到方法和类到类,这可能会使代码复杂化很多(我见过这在过去)。 对于一个简单的应用程序来说,这种方法可以做得很好。
由于缺点,这种工厂方法适用于更大的系统,另一种方法可能很有用,也就是让容器或基础架构代码/组合根管理工作单元的方法。 这是你的问题的风格。
通过让容器和/或基础设施处理这个问题,您的应用程序代码不会因为必须创建,(可选)提交和Dispose一个UoW实例而受到污染,这会使业务逻辑简单而干净(只有一项责任)。 这种方法有一些困难。 例如,你是否提交并处理实例?
处理工作单元可以在Web请求结束时完成。 然而,许多人错误地认为这也是承担工作单位的地方。 但是,在应用程序的这一点上,你根本无法确定工作单元应该实际执行。 例如,如果业务层的代码扔的被抓越往上调用堆栈异常,你绝对不希望提交。
真正的解决方案是再次明确地管理某种范围,但这次在组合根中执行。 抽象出命令/处理程序模式背后的所有业务逻辑,您将能够编写一个装饰器,该装饰器可以环绕每个允许执行此操作的命令处理程序。 例:
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容器都允许您以一致的方式配置这样一个装饰器,以便将其包装在所有ICommandHandler<T>
实现中。
我很确定这是因为DbContext并不是线程安全的。 所以分享这件事绝不是一个好主意。
这里没有一个答案实际上回答了这个问题。 OP没有询问关于单一/每应用DbContext设计的问题,他询问了每个(Web)请求设计以及可能存在的益处。
我将引用http://mehdi.me/ambient-dbcontext-in-ef6/,因为Mehdi是一个很棒的资源:
可能的性能提升。
每个DbContext实例维护从数据库加载的所有实体的第一级缓存。 无论何时通过主键查询实体,DbContext都会首先尝试从其第一级高速缓存中检索它,然后再默认从数据库查询该实体。 根据您的数据查询模式,在多个顺序业务事务中重复使用相同的DbContext可能会导致由于DbContext第一级缓存而导致的数据库查询数量减少。
它启用延迟加载。
如果您的服务返回持久化实体(与返回视图模型或其他类型的DTO相反),并且您希望利用这些实体的延迟加载,那么从中检索这些实体的DbContext实例的生存期必须超出业务交易的范围。 如果服务方法在返回之前放置了它使用的DbContext实例,那么在返回的实体上进行延迟加载属性的任何尝试都会失败(无论是否使用延迟加载是一个好主意,这是一个完全不同的讨论,我们将不会涉及这里)。 在我们的Web应用程序示例中,延迟加载通常用于由单独服务层返回的实体上的控制器操作方法。 在这种情况下,服务方法用于加载这些实体的DbContext实例需要在Web请求期间(或者至少在操作方法完成之前)保持活动状态。
请记住,也有缺点。 该链接包含许多其他资源来阅读这个主题。
只要发布此信息,以防其他人绊倒这个问题,而没有专注于并未解决问题的答案。
链接地址: http://www.djcxy.com/p/33509.html上一篇: One DbContext per web request... why?
下一篇: what is the difference between linq to sql class and entity framework