我已经阅读了很多文章,解释如何设置实体框架的DbContext,以便使用各种DI框架只创建和使用每个HTTP web请求。
为什么一开始这是个好主意?使用这种方法有什么好处?在某些情况下,这是个好主意吗?在每个存储库方法调用实例化dbcontext时,是否有一些事情是使用这种技术可以做而不能做的?
我已经阅读了很多文章,解释如何设置实体框架的DbContext,以便使用各种DI框架只创建和使用每个HTTP web请求。
为什么一开始这是个好主意?使用这种方法有什么好处?在某些情况下,这是个好主意吗?在每个存储库方法调用实例化dbcontext时,是否有一些事情是使用这种技术可以做而不能做的?
当前回答
我很确定这是因为DbContext根本不是线程安全的。所以分享从来不是个好主意。
其他回答
这里没有一个答案能真正回答这个问题。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).
记住,也有缺点。这个链接包含了许多关于这个主题的其他资源。
我把这篇文章贴出来,以防别人偶然发现这个问题,并没有被那些并没有真正解决这个问题的答案所吸引。
注意:这个答案谈论的是实体框架的DbContext,但是 它适用于任何类型的工作单元实现,例如 LINQ到SQL的DataContext,以及NHibernate的ISession。
Let start by echoing Ian: Having a single DbContext for the whole application is a Bad Idea. 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 since the DbContext caches data, it gets stale pretty soon. 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 (i.e. with a transient lifestyle) of the DbContext into anyone who needs it. (for more information about why a single DbContext -or even on context per thread- is bad, read this answer).
首先我要说的是,将DbContext注册为transient是可行的,但通常情况下,您希望在特定范围内拥有这样一个工作单元的单个实例。在web应用程序中,在web请求的边界上定义这样一个作用域是可行的;因此是Per Web Request生活方式。这允许您让一整套对象在同一上下文中操作。换句话说,它们在同一个业务事务中运行。
如果您没有在同一上下文中运行一组操作的目标,在这种情况下,暂时的生活方式是可以的,但有几件事情需要注意:
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). 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. 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. Since DbContext implements IDisposable, you probably still want to Dispose all created instances. 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. 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.
另一种选择是根本不注入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。
The downside is that you will have to pass around the DbContext from method to method (which is termed Method Injection). 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.
方法注入并不是那么糟糕,但是当业务逻辑变得更加复杂,涉及到更多的类时,您将不得不从一个方法传递到另一个方法,从一个类传递到另一个类,这可能会使代码变得非常复杂(我以前见过这种情况)。对于一个简单的应用程序,这种方法就可以了。
由于这种工厂方法的缺点,它适用于更大的系统,另一种方法可能会有用,那就是让容器或基础结构代码/组合根管理工作单元。这就是你的问题所涉及的风格。
通过让容器和/或基础设施处理这个问题,您的应用程序代码不会因为必须创建、(可选地)提交和处置UoW实例而受到污染,这使业务逻辑简单而干净(只有一个Single Responsibility)。这种方法有一些困难。例如,在哪里提交和处置实例?
处理一个工作单元可以在web请求的末尾完成。然而,许多人错误地认为这也是提交工作单元的地方。然而,在应用程序的这一点上,您根本不能确定工作单元实际上应该被提交。例:如果业务层代码抛出了一个异常,并在调用堆栈的上层被捕获,你肯定不想提交。
真正的解决方案仍然是显式地管理某种范围,但这次在Composition Root中进行。将命令/处理程序模式背后的所有业务逻辑抽象出来,您将能够编写一个装饰器,该装饰器可以封装在允许执行此操作的每个命令处理程序周围。例子:
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。
一个建议是“Dispose DbContexts as soon as possible” 因为DbContext激活会占用有价值的资源,比如db 连接等等…… 另一种是每个请求一个DbContext 推荐的
这些相互矛盾,因为如果你的Request做了很多与Db无关的事情,那么你的DbContext就会被毫无理由地保留。 因此,当你的请求只是等待随机的东西完成时,让你的DbContext保持活跃是浪费…
所以很多遵循规则1的人在他们的“存储库模式”中有他们的DbContext,并为每个数据库查询创建一个新的实例,即每个请求X*DbContext
它们只是尽快获取数据并处理上下文。 许多人认为这是一种可以接受的做法。 虽然这有占用你的db资源的最低时间的好处,但显然牺牲了所有的UnitOfWork和缓存糖果EF提供。
保持DbContext的一个多用途实例最大限度地提高了缓存的好处,但由于DbContext不是线程安全的,每个Web请求都运行在它自己的线程上,每个请求的DbContext是你能保持的最长时间。
所以EF的团队建议每个请求使用1 Db Context,这显然是基于这样一个事实:在一个Web应用程序中,一个UnitOfWork最有可能是在一个请求中,这个请求有一个线程。所以每个请求一个DbContext就像UnitOfWork和缓存的理想好处。
但在很多情况下,情况并非如此。 我认为记录一个单独的UnitOfWork,因此有一个新的DbContext后请求记录在异步线程是完全可以接受的
最后,DbContext的生命周期被限制在这两个参数上。UnitOfWork和Thread
在这个问题或讨论中没有真正解决的一件事是DbContext不能取消更改的事实。您可以提交更改,但不能清除更改树,因此如果您使用了每个请求上下文,那么无论出于何种原因需要丢弃更改,都是不走运的。
Personally I create instances of DbContext when needed - usually attached to business components that have the ability to recreate the context if required. That way I have control over the process, rather than having a single instance forced onto me. I also don't have to create the DbContext at each controller startup regardless of whether it actually gets used. Then if I still want to have per request instances I can create them in the CTOR (via DI or manually) or create them as needed in each controller method. Personally I usually take the latter approach as to avoid creating DbContext instances when they are not actually needed.
这也取决于你从哪个角度看问题。对我来说,每个请求实例从来没有意义。DbContext真的属于Http Request吗?就行为而言,这是错误的地方。您的业务组件应该创建上下文,而不是Http请求。然后,您可以根据需要创建或丢弃业务组件,而不必担心上下文的生命周期。
我同意之前的观点。可以说,如果你打算在单线程应用中共享DbContext,你将需要更多的内存。例如,我在Azure上的web应用程序(一个额外的小实例)需要另外150mb的内存,而我每小时大约有30个用户。
这是一个真实的例子:应用程序已经在中午12点部署