我一直在阅读很多文章,解释如何设置实体框架,以便使用各种 DI 框架为每个 HTTP Web 请求创建一个和使用实体框架。DbContext
为什么这首先是一个好主意?使用这种方法可以获得哪些优势?在某些情况下,这是一个好主意吗?使用这种技术可以执行哪些操作,而在实例化每个存储库方法调用时无法执行的操作?DbContext
注意:这个答案讨论的是实体框架的
DbContext
,但它
适用于任何类型的工作单元实现,例如
LINQ to SQL 的DataContext
和 NHibernate 的ISession
。
让我们从呼应 Ian 开始:为整个应用程序使用单个是一个坏主意。唯一有意义的情况是,当您有一个单线程应用程序和一个仅由该单个应用程序实例使用的数据库时。这不是线程安全的,并且由于缓存数据,因此很快就会过时。当多个用户/应用程序同时在该数据库上工作时,这会给您带来各种麻烦(当然,这很常见)。但我希望你已经知道这一点,只是想知道为什么不向任何需要它的人注入一个新的实例(即具有短暂的生活方式)。(有关为什么单个 – 甚至每个线程的上下文 – 不好的更多信息,请阅读此答案)。DbContext
DbContext
DbContext
DbContext
DbContext
首先,我要说的是,将 a 注册为瞬态可以工作,但通常您希望在特定范围内拥有此类工作单元的单个实例。在 Web 应用程序中,在 Web 请求的边界上定义这样的范围是可行的;因此,每个 Web 请求的生活方式。这允许您让一组整组对象在同一上下文中运行。换句话说,它们在同一业务交易中运行。DbContext
如果您没有让一组操作在同一上下文中运行的目标,在这种情况下,瞬态生活方式很好,但有几点需要注意:
_context.SaveChanges()
DbContext
DbContext
IDisposable
context.SaveChanges()
另一种选择是根本不注入 a。相反,你注入一个能够创建一个新实例(我过去曾经使用过这种方法)。这样,业务逻辑就可以显式控制上下文。如果可能看起来像这样: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
方法注入并不是那么糟糕,但是当业务逻辑变得更加复杂,并且涉及更多的类时,您将不得不将其从一个方法传递到另一个方法,从一个类传递到另一个类,这可能会使代码复杂化很多(我过去见过这种情况)。对于一个简单的应用程序,这种方法可以很好地工作。
由于缺点,这种工厂方法适用于更大的系统,另一种方法可能很有用,那就是让容器或基础设施代码/组合根管理工作单元的方法。这就是你的问题所关注的风格。
通过让容器和/或基础结构处理这一点,您的应用程序代码不会因必须创建、(可选)提交和释放 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>
微软有两个相互矛盾的建议,许多人以完全不同的方式使用DbContexts。
这些是相互矛盾的,因为如果您的请求正在做很多与 Db 内容无关的事情,那么您的 DbContext 就会无缘无故地保留。
因此,当您的请求只是等待随机内容完成时,保持 DbContext 处于活动状态是浪费……
因此,许多遵循规则 1 的人将他们的 DbContext 放在他们的“存储库模式”中,并为每个数据库查询创建一个新实例,因此每个请求的 X*DbContext
他们只是获取数据并尽快处理上下文。
许多人认为这是一种可以接受的做法。
虽然这样做的好处是在最短时间内占用数据库资源,但它显然牺牲了 EF 必须提供的所有工作单元和缓存糖果。
保持 DbContext 的单个多用途实例处于活动状态可以最大限度地发挥缓存的好处,但由于 DbContext 不是线程安全的,并且每个 Web 请求都在其自己的线程上运行,因此每个请求的 DbContext 是可以保留的最长的。
因此,EF 团队关于每个请求使用 1 Db 上下文的建议显然是基于这样一个事实,即在 Web 应用程序中,UnitOfWork 很可能位于一个请求中,并且该请求有一个线程。因此,每个请求一个 DbContext 就像工作单元和缓存的理想优势。
但在许多情况下,情况并非如此。
我认为记录一个单独的工作单元,因此在异步线程中为请求后日志记录提供新的 DbContext 是完全可以接受的
因此,最后它拒绝了 DbContext 的生存期仅限于这两个参数。工作单元和线程
这里没有一个答案实际上回答了这个问题。OP没有询问单例/每应用程序DbContext设计,他询问了每(Web)请求设计以及可能存在的潜在好处。
我将参考 http://mehdi.me/ambient-dbcontext-in-ef6/ 因为Mehdi是一个很棒的资源:
可能的性能提升。
每个 DbContext 实例都维护一个第一级缓存,其中包含其从数据库加载的所有实体。每当按主键查询实体时,DbContext 将首先尝试从其第一级缓存中检索该实体,然后再默认为从数据库查询该实体。根据您的数据查询模式,由于 DbContext 第一级缓存,在多个顺序业务事务中重用相同的 DbContext 可能会导致数据库查询减少。
它启用延迟加载。
如果您的服务返回持久实体(而不是返回视图模型或其他类型的 DTO),并且您希望利用这些实体上的延迟加载,则从中检索这些实体的 DbContext 实例的生存期必须超出业务事务的范围。如果服务方法在返回之前释放了它使用的 DbContext 实例,则任何对返回的实体进行延迟加载属性的尝试都将失败(使用延迟加载是否是一个好主意是一个完全不同的争论,我们不会在这里讨论)。在我们的 Web 应用程序示例中,延迟加载通常用于由单独服务层返回的实体上的控制器操作方法。在这种情况下,服务方法用于加载这些实体的 DbContext 实例需要在 Web 请求期间保持活动状态(或至少在操作方法完成之前)。
请记住,也有缺点。该链接包含有关该主题的许多其他资源。
只是发布这个以防其他人偶然发现这个问题并且没有被实际上没有解决该问题的答案所吸引。
模板简介:该模板名称为【C#每个 Web 请求一个 DbContext…为什么?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。