我应该在哪里创建我的服务中的DbContext?

时间:2016-09-12 03:24:23

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

我的应用程序是围绕"服务"像这样:

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的实例并且不使用它,是否有任何费用?或者对象是否只是实例化以供将来使用?我不想在这里征求意见,因为我知道这是不允许的,但这是朝着保持干燥的正确方向迈出的一步吗?

1 个答案:

答案 0 :(得分:4)

工作单位模式

DbContext实际上是' unit of work'的实现。模式 - 创建DbContext后,当您致电DbSet时,对SaveChanges所做的所有更改都会一次性保留。

因此,为了正确回答您的问题,您需要回答的进一步问题是:构成您工作单元的更改范围是什么?换句话说 - 需要以原子方式进行哪些更改 - 所有更改都成功,或者全部失败?

实用(如果有这样的示例 - 假设您有一个API端点,它公开允许客户提交订单的操作。控制器使用OrderService提交订单,然后InventoryService更新与中的项目关联的库存如果每个服务都有自己的DbContext,那么OrderService就有可能成功保留订单提交,但InventoryService将无法保持库存更新。

依赖注入

为了解决这个问题,一个常见的模式是创建一个上下文 per-request ,让你的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将用于所有请求/线程 - 这可以{{3}并且应该避免。
    • (注意:如果您不使用DI并在控制器中构建新的服务实例,这不是问题,但是您在控制器级别也失去了DI的好处......)
  • 现在所有服务都被锁定为使用相同的DbContext实例 - 如果将来您决定拆分数据库并且某些服务需要访问不同的DbContext,该怎么办?你会创建两个不同的BaseServices吗?或传递配置数据以允许基本服务切换? DI会处理这个问题,因为你只需要注册两个不同的Context类,然后容器就会为每个服务提供所需的上下文。
  • 你回到IQueryable的任何地方吗?如果你是,那么你冒着IQueryable即使在DbContext超出范围之后也会导致Db命中的风险 - 它可能已被垃圾收集器处理掉而无法使用。

从开发的角度来看,我认为没有什么比DI方法更简单了 - 只需在构造函数中指定DbContext,然后让DI容器容器处理其余部分。

如果您在每个请求中使用DbContext,您甚至不必创建或处置上下文,并且您可以确信IQueryables可以在请求调用堆栈中的任何位置解析。

如果使用Mehdi的方法,则必须创建DbContextScope,但如果您要沿着存储库模式路径并且希望显式控制上下文范围,那么该方法更合适。

正如您所看到的,我不太关心构建DbContext时不需要的计算成本(据我所知,它是相当低的成本,直到你实际上使用它来命中数据库),并且更关注允许单元测试和脱离依赖关系的应用程序架构。