【问题标题】:One DbContext per request in ASP.NET MVC (without IOC container)ASP.NET MVC 中的每个请求一个 DbContext(没有 IOC 容器)
【发布时间】:2011-09-14 03:27:07
【问题描述】:

抱歉,如果这个问题已经得到解答,但是如果您不使用 IOC 容器,如何保证每个请求一个实体框架 DbContext? (到目前为止,我遇到的答案都是关于 IOC 容器解决方案的。)

似乎大多数解决方案都与HttpContext.Current.Items 字典挂钩,但如何保证在请求完成后处理 DbContext? (或者使用 EF DbContext 处理不是绝对必要的吗?)

编辑

我目前正在我的控制器中实例化和处理我的 DbContext,但我在 ActionFilters 和我的 MembershipProvider 中也有几个单独的 DbContext 实例(我刚刚注意到,还有几个验证器)。因此,我认为集中实例化和存储我的 DbContext 以减少开销可能是个好主意。

【问题讨论】:

  • 这是否与工作单元模式有关,或者.......这是一个潜在的问题,不想将上下文传递给将使用它的多个对象?跨度>

标签: asp.net-mvc entity-framework asp.net-mvc-3 entity-framework-4.1 ef-code-first


【解决方案1】:

我知道这不是最近的问题,但无论如何我都会发布我的答案,因为我相信有人可能会觉得它很有用。

可能与许多其他人一样,我遵循了已接受答案中提到的步骤。是的,它有效。 但是,有一个问题:

方法 BeginRequest() 和 EndRequest() 每次发出请求时触发,但不仅适用于 aspx 页面,还适用于所有静态内容!也就是说,如果您使用上面提到的代码并且您的页面上有 30 张图片,那么您将重新实例化您的 dbcontext 30 次!

解决方案是使用包装类来检索上下文,如下所示:

internal static class ContextPerRequest
{
      internal static DB1Entities Current
      {
          get
          {
              if (!HttpContext.Current.Items.Contains("myContext"))
              {
                  HttpContext.Current.Items.Add("myContext", new DB1Entities());
              }
              return HttpContext.Current.Items["myContext"] as DB1Entities;
          }
      }
 }

然后进行处理

protected void Application_EndRequest(object sender, EventArgs e)
{
   var entityContext = HttpContext.Current.Items["myContext"] as DB1Entities;
   if (entityContext != null) 
      entityContext.Dispose();
}

此修改可确保您在每个请求中仅在需要时实例化和处置上下文一次。选定的答案每次都会实例化上下文。

注意: DB1Entities 派生自 DbContext(由 VS 生成)。你可能想用你的上下文名称来改变它;)

注意 2: 在此示例中,我只使用一个 dbcontext。如果您需要使用多个,则需要根据需要修改此代码。不要把它当作世界问题的最终解决方案,因为它肯定不是最终产品。它只是为了提供一个提示,如何以一种非常简单的方式实现它。

注意 3: 同样的方法也可以在不同的情况下使用,例如,当您想要共享 SqlConnection 的实例或任何其他... DbContext 对象,也不是实体框架。

【讨论】:

  • 这很好,将静态 Current 方法放在 Context 类中,这对我来说是完美的 :)
  • 我想使用那个解决方案,但是我在解决方案中有另一个项目处理数据库访问,所以我不能使用 HttpContext。有什么方法可以实施您的建议吗?
  • @MattheW,如果你能以某种方式将你的数据库管理类暴露给 UI,那么你可以在请求结束时处理它,那么可以。整个问题是关于创建单个上下文并在请求结束之前共享它,因此您不会在单个用户请求期间一遍又一遍地重新创建它。您只是不关心它 - 您按需实例化它并在完成后将其处理掉,然后将结果发送给用户。
  • 嗨 Walther - 我很困惑我一直在阅读,将您的上下文存储在静态属性中会产生一些令人讨厌的错误 - 是这样吗?参考:stackoverflow.com/questions/4847892/…
  • @Loren,不,因为在我的解决方案中,上下文存储在 HttpContext.Current.Items 集合中,并在 request end 事件期间被释放。静态属性仅从集合中公开此上下文,并在它尚不存在时将其实例化。它永远不会存储在静态变量中,并且是特定于当前上下文的,因此您不会遇到您提到的问题。您也可以将属性转换为方法,并且所有功能都将完全相同。我只是更喜欢属性的语法,所以我用它来代替方法。
【解决方案2】:

我会使用 BeginRequest/EndRequest 方法,这有助于确保在请求结束时正确处理您的上下文。

protected virtual void Application_BeginRequest()
{
    HttpContext.Current.Items["_EntityContext"] = new EntityContext();
}

protected virtual void Application_EndRequest()
{
    var entityContext = HttpContext.Current.Items["_EntityContext"] as EntityContext;
    if (entityContext != null)
        entityContext.Dispose();
}

在你的 EntityContext 类中......

public class EntityContext
{
    public static EntityContext Current
    {
        get { return HttpContext.Current.Items["_EntityContext"] as EntityContext; }
    }
}

【讨论】:

  • 当我在控制器中声明一个私有实例(私有 EntityContext _db = EntityContext.Current;)并在所有操作中使用它时,我观察到内存泄漏。知道为什么吗?
  • 我已经尝试过了,但似乎如果剃刀视图需要额外的调用,那么我们会得到一个异常,即对象上下文被暴露并且我们正在尝试访问数据库。看起来像在剃刀视图完成渲染之前调用了 EndRequest?我可能做错了什么?
【解决方案3】:

一种方法是订阅 Application_BeginRequest 事件,将 DbContext 注入当前 HttpContext 并在 Application_EndRequest 从 HttpContext 中获取并处理。介于两者之间的任何东西(几乎是所有东西:-))都可以从当前的 HttpContext 获取 DbContext 并使用它。而且,是的,你应该处理它。顺便说一句,你有什么理由不使用已经为你做这件事的 DI 框架以及其他有用的东西?

【讨论】:

  • +1 感谢您的回答。这是有道理的。我(目前)没有使用 IOC 容器,因为该应用程序相对简单,而且我还没有遇到任何远程复杂的依赖注入场景。我几乎只是将我的 DbContext 注入到各种服务中。但我并不反对使用 IOC 容器。
  • 你的应用是否简单并不重要。这是面向未来的。现在设置它虽然它很简单,然后当你的应用变得更大时,它会更容易。除非你的应用程序当然会保持小规模。
  • @RPM1984,我明白你的意思了......该项目可能不会再发展更多的深度层,但会扩大广度。换句话说,我希望添加更多页面,但我基本上有模型、服务、控制器、视图和验证器,我不希望这种情况发生变化。现在,很容易将所有东西连接起来(事实上,其中大部分是由 MVC 自动化的)。我无法了解 IOC 容器如何让我的生活更轻松,但我很想知道您的策略是什么。
  • 对我来说很简单。您应该始终使用接口驱动编程,至少对单个组件进行单元测试。然后,一旦您使用接口,创建一个基本的 DI 注册表来设置依赖项。
  • @DanM - 好吧,那是你的偏好。但就我个人而言,我会(并且有)这样的测试:确保返回 POST 重定向结果中的有效模型(例如测试 PRG),确保返回视图结果中的无效模型并且模型状态包含错误等。对于这些测试,_service 应声明为IService 并在您的测试项目中实现为ITestService,以便于测试。无论如何,咆哮 - 这取决于你。 :)
【解决方案4】:

乍得莫兰回答的小补充。它的灵感来自瓦尔特笔记。为避免静态内容的上下文初始化,我们应该检查当前路由处理程序(此示例仅适用于 MVC):

protected virtual void Application_BeginRequest()
{
  var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(this.Context));
  if (routeData != null && routeData.RouteHandler is MvcRouteHandler)
  {
    HttpContext.Current.Items["_EntityContext"] = new EntityContext();
  }
}

【讨论】:

  • 谢谢。我更喜欢这个解决方案而不是全局静态方法。此外,如果您改为钩住Application_PreRequestHandlerExecute,则只需检查this.Context.Handler is MvcHandler,因为它将在处理管道中的此时设置。
【解决方案5】:

如果您在控制器中实现 IDisposable,并在 disposing 方法中释放上下文,并在控制器构造函数中实例化新上下文,那么您应该是安全的,因为控制器是为每个请求实例化的。 但是,我不明白,您为什么要这样做? ... 您应该使用 DI,或者使用一个静态上下文实例创建一个上下文工厂。如果您不使用一个实例(您为每个请求创建一个),那么您在某些时候会遇到问题。未处理上下文的问题是 EF 在上下文中缓存数据,并且如果其他上下文实例更改了数据库中已经缓存在另一个上下文中的某些内容 - 您的状态不一致。在 DI 变得如此流行之前,我曾经在应用程序的某个地方有一个静态的上下文实例,这比让每个请求都创建自己的上下文要快得多,也更安全,但是你需要实现状态检查代码来确保上下文连接到db没问题...这个问题有很多更好的解决方案,最好是使用一些DI框架。我建议将 Ninject 与 MVCTurbine 结合使用,它易于设置,您可以通过 NuGet 进行添加。

【讨论】:

  • 我没有听懂你的意思。使用 DI 框架如何防止不一致的状态?如果两个用户试图同时编辑同一个记录,我看不出共享一个上下文或使用两个单独的上下文会有什么不同。其中一位用户(最先提交的用户)将遇到意想不到的结果。
  • 示例:假设我决定编辑您的答案。我单击“编辑”按钮,现在我正在查看一个包含您的答案的编辑框。我改了几句。不过,在我点击提交之前,您决定编辑您的答案。现在您正在查看一个编辑框,但您看到的答案没有我所做的任何单词更改。现在我提交我的答案。您自己进行一些更改,然后提交您的答案。结果?不一致的状态。即使我们共享一个数据上下文,这也无关紧要,因为数据上下文在用户提交之前并不知道已经进行了更改。
  • 示例(续):谢天谢地,StackOverflow 保存了整个编辑历史记录,所以即使我的更改被你的覆盖,我仍然可以回去查看我写的内容。
  • 是的,这并不容易解决,但是如果您需要让系统处理这种情况,您必须在数据库级别处理它 - 将版本控制添加到数据库中的记录中。当您尝试创建新版本时,您会遇到其他用户制作的版本,然后您可以要求用户决定忽略/覆盖等。
  • 我的意思是当您让用户编辑复杂对象时,您会向他展示一些下拉列表,以便他/她可以在表单上选择值。如果管理员在用户的 dbcontext 已经读取该表之后更改了代码簿(在下拉列表中添加/删除值),它将不会反映在现有的上下文中 - 这就是为什么您需要确保为每个请求构建新的上下文,如果你想拥有当前数据。缺点是性能 - 每次都从数据库中读取值。您需要决定做出哪种权衡 - 如果您不想考虑交易和并发...
【解决方案6】:

这里的滑坡状态不一致。如果您的应用程序将有多个用户,并且他们有可能同时更改数据,那么如果您保留单个上下文,您可能会开始遇到数据完整性问题。

【讨论】:

  • mymex,不一致的状态绝对是一个问题,但我看不出用户共享单个数据上下文或使用单独的上下文有什么不同。正如我对 obrad 所说,如果两个用户同时编辑同一条记录,那么第一个提交的用户将让第二个提交的用户覆盖他的更改。
  • mymex,也请在 obrad 的回答中查看我的 cmets。
猜你喜欢
  • 1970-01-01
  • 2018-05-22
  • 1970-01-01
  • 2011-07-12
  • 1970-01-01
  • 2019-02-12
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
相关资源
最近更新 更多