工作单元模式
DbContext 实际上是“unit of work”模式的一种实现——一旦创建了DbContext,所有对DbSet 所做的更改都会在您调用SaveChanges 时一次性持久化。
因此,为了正确回答您的问题,您需要回答的进一步问题是:构成您的工作单元的更改范围是什么?换句话说 - 需要以原子方式进行哪些更改 - 全部成功还是全部失败?
一个实用的(如果是这样的例子 - 假设您有一个 API 端点公开一个允许客户端提交订单的操作。控制器使用 OrderService 提交订单,然后使用 InventoryService 更新与项目关联的库存如果每个服务都有自己的 DbContext,则存在 OrderService 将成功持久化订单提交,但 InventoryService 将无法持久化库存更新的风险。
依赖注入
为了解决这个问题,一种常见的模式是创建一个上下文每个请求,让您的 IoC 容器创建和处置上下文,并使其可用于根据请求注入服务。 This blog post 提供了一些 DbContext 管理选项,并包含一个配置 Ninject 的示例。
这意味着你的 ctor 看起来像:
public ProfileService(CommService commService, AppContext context) {
_commService = commService;
_context = context;
}
您可以在那里安全地使用上下文,而不必担心它是如何创建或来自哪里的。
Medhi 的 DbScopeFactory
>
但是,对于更复杂的应用程序,我首选的方法是在此处记录的优秀开源库:http://mehdi.me/ambient-dbcontext-in-ef6/。为每个请求注入 DbContext 对于更简单的应用程序来说可以正常工作,但随着您的应用程序越来越多地参与(例如每个应用程序多个上下文、多个数据库等),他的IDbContextScopeFactory 提供的更精细的控制是无价的。
编辑添加 - 注入与构造的优缺点
根据您的评论询问您提出的方法的优缺点,我想说的是,通常情况下,注入依赖项(包括 DbContext)是一种更加灵活和强大的方法,并且仍然可以实现确保您的开发人员的目标不必关心 dbcontext 生命周期管理。
dependency injection 的所有实例的优缺点通常是相同的,不仅是 db 上下文,还有一些在服务中构建上下文的具体问题(即使是在基础服务中):
- 每个服务都有自己的 dbcontext 实例 - 如果您的工作单元跨越多个服务执行的任务,这可能会导致一致性问题(参见上面的示例)
- 对您的服务进行单元测试将更加困难,因为它们正在构建自己的依赖项。注入 dbcontext 意味着您可以 mock it in your unit tests 并在不访问数据库的情况下测试功能
- 它将非托管状态引入您的服务 - 如果您使用依赖注入,您希望 IoC 容器管理服务的生命周期。当您的服务没有每个请求的依赖项时,IoC 容器将为整个应用程序创建服务的单个实例,这意味着您保存到私有成员的 dbcontext 将用于所有请求/线程 - 这可以be a big problem 和应该避免。
- (注意:如果您不使用 DI 并在控制器中构建服务的新实例,这不是什么大问题,但是您也会在控制器级别失去 DI 的好处...)
- 所有服务现在都锁定为使用相同的 DbContext 实例 - 如果将来您决定拆分数据库并且某些服务需要访问不同的 DbContext,该怎么办?你会创建两个不同的 BaseServices 吗?还是传入配置数据让基础服务切换? DI 会解决这个问题,因为您只需注册两个不同的 Context 类,然后容器将为每个服务提供所需的上下文。
- 您是否在任何地方返回
IQueryables?如果是这样,那么即使在 DbContext 超出范围后,IQueryable 也会导致 Db 命中 - 它可能已被垃圾收集器处理并且不可用。
从开发人员的角度来看,我认为没有什么比 DI 方法更简单的了 - 只需在构造函数中指定 DbContext,然后让 DI 容器容器处理其余的事情。
如果您对每个请求都使用 DbContext,您甚至不必创建或处置上下文,并且您可以确信 IQueryables 将在请求调用堆栈中的任何位置解析。
如果您使用 Mehdi 的方法,则必须创建一个 DbContextScope,但如果您正在使用存储库模式路径并希望显式控制上下文范围,则该方法更合适。
如您所见,我不太关心在不需要时构建 DbContext 的计算成本(据我所知,在您实际使用它来访问数据库之前,它的成本相当低),并且更关注允许单元测试和从依赖项中解耦的应用程序架构。