在 MVC 中实例化和处置 DbContext 的最佳方法是啥?

Posted

技术标签:

【中文标题】在 MVC 中实例化和处置 DbContext 的最佳方法是啥?【英文标题】:What is the best way to instantiate and dispose DbContext in MVC?在 MVC 中实例化和处置 DbContext 的最佳方法是什么? 【发布时间】:2011-10-22 16:58:59 【问题描述】:

MVC 3 + EF 4.1

我在两种处理 DbContext 的方法之间进行选择:

    Application_BeginRequest中实例化,放入 HttpContext.Current.Items 并在 Application_EndRequest 中处理。 创建一次性 UnitOfWork(DbContext 的包装类型)和 使用using(var unitOfWork = new UnitOfWork()) ... 启动每个控制器操作

请分享您的经验:您更喜欢哪一种?每种方法的优缺点是什么?

【问题讨论】:

使用块方法有一些缺点。它会导致大量的数据库往返和实体框架中的事务滥用。参考ayende.com/blog/4775/… 为什么会导致更多的往返?在大多数情况下,一个 http 请求应该运行一个操作,所以如果你将整个操作的代码包装到这个 using 块中,与第一种方法相比,不会有更多的数据库请求。 “按操作”方法的另一件事是,您应该始终了解可能调用数据库的范围并适当地放置块。例如,如果您的模型包含一些要在视图渲染时延迟加载的集合,则返回 View(Model) 的语句应该在块内。 如果您在控制器层中使用 DbContext,使用 UnitOfWork 包装会在 UI 层和您的数据库方法中创建强依赖性。然后你需要一个服务层和存储库层。之后,如果您的存储库有单独的 UnitOfWork 并使用块,那将是一个问题。因为每个存储库都会创建事务和不必要的数据库往返。有关更多详细信息,请参见上面的链接。如果您确定每个请求有一个服务调用,那么您可以在服务方法中使用 unitofwork。但是,这不是保证。 每个 http 请求可能有 2 个或更多的服务调用,但它们最有可能在同一个操作方法中。因此,一旦将它们全部包装在单个 UnitOfWork 下,它们就会共享一个 DbContext。是的,即使具有相同的 DbContext,它们也可能在单独的事务下一个接一个地运行,但第一种方法的工作方式相同 如果其中一项交易失败会怎样?你能恢复另一个还是那些是独立的?那就是问题所在。另外,如果你这样做,你的 UI 层将依赖于实体框架,不是吗? 【参考方案1】:

我建议您使用依赖注入框架。您可以根据要求注册您的DbContext

 container.RegisterType<MyDbContext>().InstancePerHttpRequest();

并将其作为构造函数参数注入控制器。

public class MyController : Controller

    public MyController(MyDbContext myDbContext)
    
         _myDbContext = myDbContext;
    

如果注册的类型实现了IDisposable,那么DI框架会在请求结束时释放它。

第一种方法:使用 ID 框架比手动实现要干净得多。此外,您的所有请求可能都不需要您的 UoW。

第二种方法:控制器不应该知道如何构造你的 UoW(DbContext)。目的不是减少组件之间的耦合。

【讨论】:

对,使用 IoC 容器比处理 BeginRequset 和 EndRequest 更好,但在我看来它仍然非常接近数字 1,这就是为什么我什至没有将它作为单独的方法提取的原因。对我来说更重要的是比较两种访问和控制 DbContext 生命周期的方法(=工作单元):第一种方法意味着 ASP.NET/IoC 基础设施负责它,第二种方法是关于每个控制器动作收费。 @YMC 已编辑答案。您的第二种方法将在您的控制器和 UoW 实现之间引入耦合。我建议你避免它。 引入服务层,向Controller注入服务。服务可以有许多存储库,并且存储库依赖于使用 EFDbContext 包装的 UnitOfWork。然后使用 DI 注入这些依赖项。然后您可以获得关注点分离的优势。 @Eranga:这是否意味着只有在调用实际上将上下文作为构造函数参数的控制器时才会实例化上下文?例如:如果我有一个以MyController() 作为构造函数的控制器,则不会实例化上下文,对吗?如果两个控制器在一个请求中被调用(例如可以通过使用RenderAction 来实现)并且两个控制器都有一个上下文作为参数,那么只会创建一个上下文并将其注入到两个控制器中,对吗?上面的例子不是 Unity,是吗?不知道Unity2是否也提供了这样的逻辑? @Slauma 是的。 DI 框架负责处理它。示例使用Autofac。我在Silk 上看到了Unity 的“每个请求生命周期”实现【参考方案2】:

我们目前使用通过存储库工厂中的服务定位器实例化的 UoW(工作单元)注入的存储库。 Unity 以这种方式控制生命周期,让您远离工作。

如果您使用 POCO、实体对象等,您的特定实现会有所不同。

如果您要在控制器中使用多个对象集以确保您只使用一个上下文,那么最终您需要 UoW。这将检查您的交易等。

如果您要使用多个对象上下文(即多个 EDMX),您会希望考虑将 UoW 与 MSDTC 结合使用……但这可能比您想知道的要多。最后,重要的是确保您只实例化控制器操作所需的内容(即上下文的一个实例。)。我认为我不会使用 Begin_Request,您甚至可能不需要每个请求的上下文。

【讨论】:

【参考方案3】:

不要将 DbContext 放在 global.asax 中! :

    Static field of DbContext in Global.asax versus instance field of DbContext in controller class? Entity framework context as static

【讨论】:

OP 没有说将 DbContext 放在静态字段中......他想把它放在 HttpContext.Current.Items 中,据我所知这是非常安全的 我没有说我把 DbContext 放到 global.asax 中。我说我放置了实例化和处理 DbContext 的代码。 DbContext 位于 HttpContext.Current.Items 中。它是线程安全的。

以上是关于在 MVC 中实例化和处置 DbContext 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

通过 Azure 函数注入 DbContext 时无法访问已处置的对象

在 ASP.NET Core Authorize-Attribute 中使用带有 DbContext 的存储库:“无法访问已处置的对象”

C#:使用 IQueryable 注入 DbContext 时,无法访问 ASP.NET Core 中的已处置对象

Entity Framework 和 MVC 在业务层或数据访问层创建 DbContext

使用实体框架6的mvc4中的单个或多个DbContext文件

MVC项目中找不到DbContext命名空间