【问题标题】:Where should I create my DbContext among my services?我应该在我的服务中哪里创建我的 DbContext?
【发布时间】:2016-09-12 03:24:23
【问题描述】:

我的应用程序是围绕“服务”构建的,如下所示:

public class ProfileService : BaseService {

    private CommService _commService;

    public ProfileService(CommService commService) {
        _commService = commService;
    }

    public ApiResponseDto GetProfile(string profileUsername) {
        using (var db = new appContext()){ db.DoStuff(); }
    }
}

我想做的是将db 实例化推入BaseService,但我不想创建dbContext 并在我不需要它时产生这样的成本。所以我正在考虑做这样的事情:

public class BaseService {
    public AppContext _db;

    public AppContext db(){
       return _db ?? new AppContext();
    }
}

然后我的所有方法都将通过db().DoStuff() 访问数据库。

我不喜欢到处都有括号的想法,但我更喜欢清理我的服务足迹的想法。

我的问题是 - 如果我创建一个 DbContext 的实例并且不使用它,是否有任何费用?还是只是实例化了对象以供将来使用?我不想在这里征求意见,因为我知道这是不允许的,但这是朝着保持干燥的正确方向迈出的一步吗?

【问题讨论】:

  • 您是否可以考虑使用具有固有IDisposable 的第二个作用域(使用DbContext 作为属性)并实现Dispose
  • @kcwu - 据我了解,不明确处理上下文不是问题。 blog.jongallant.com/2012/10/…

标签: c# .net entity-framework ef-code-first


【解决方案1】:

工作单元模式

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 的计算成本(据我所知,在您实际使用它来访问数据库之前,它的成本相当低),并且更关注允许单元测试和从依赖项中解耦的应用程序架构。

【讨论】:

  • 谢谢 Chris - 在写这篇文章之前,我实际上阅读了 Mehdi 的帖子。您如何看待我在问题中概述的方法?您介意详细说明那里的优点/缺点吗?我的目标是避免在不需要的地方使用 dbContext,并简化服务的实现细节,这样我的开发人员就不必担心了。
  • 我对那个库的问题是我有另一个可能会消失或停止维护的依赖项。
  • 编辑添加了一些关于优点/缺点的进一步评论。
  • 这太棒了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多