深入理解 MVC .net 中的延迟加载和处理错误

Posted

技术标签:

【中文标题】深入理解 MVC .net 中的延迟加载和处理错误【英文标题】:Deep understanding of lazy loading and disposing error in MVC .net 【发布时间】:2014-06-16 23:59:04 【问题描述】:

我试图为以下问题写一个完整的详细答案: Why does "Dispose" work, and not "using(var db = new DataContext())"?

所以我设置了我的项目,其中包括:

使用实体框架的部门和员工

所以我的操作方法是这样的:

public ActionResult Index()
    

        IEnumerable<department> d;
        using (var ctx = new ApplicationDbContext())
        

            d = ctx.departments;

        


        return View(d);

很自然地期望这会导致常见错误:

The operation cannot be completed because the DbContext has been disposed

当我想解决它时,我做了以下 [强制加载而不是简单加载]:

 public ActionResult Index()
    

        IEnumerable<department> d;
        using (var ctx = new ApplicationDbContext())
        

            d = ctx.departments.toList();

        


        return View(d);

所以我试图理解底层的东西并查看 View() 方法的返回类型。我得出了以下“正确”假设:

1- 在 using 语句中以延迟加载方式调用模型 [d]。

2- 所以当模型 [d] 被发送到视图以生成页面时,DbContext 已经被 using 语句的最后一个花括号处理了。

3- 我们通过将模型 [d] 以预加载方式发送到视图来解决这种情况。

然后我继续我的假设被证明是“错误”如下:

4- 因为 View() 方法返回 ViewResult 对象,它也是一个 ActionResult..那么我可以在 using 语句中生成这个对象,然后将它返回给用户。

所以我做了以下事情:

public ActionResult Index()
    

        ActionResult myView;

        using (var ctx = new ApplicationDbContext())
        

            IEnumerable<department> d = ctx.departments;

            myView =  View(d);

        


        return myView;
    

所以我现在告诉自己,当我运行它时,ViewResult 对象 [myView] 将已经创建并返回给用户并且不会遇到任何错误。

但是我很惊讶发生了同样的错误:

The operation cannot be completed because the DbContext has been disposed

我很惊讶这种延迟加载真的很懒,而且只在最后一刻加载。

所以我继续我的“错误”假设如下:

5- 可能我需要强制 View() 方法在 using 语句中执行结果。所以我使用了ExecuteResult(ControllerContext)的方法。

现在我认为我可以运行操作方法而不会出现任何错误 但同样的错误又发生了:

The operation cannot be completed because the DbContext has been disposed.

所以我现在的问题是:

延迟加载查询的执行在MVC框架的什么地方发生!!

或者让我把我的问题改写如下:

为什么 View(d) 方法在 [d] 对象不在 using 语句时迭代它,而不是在 view(d) 方法在 using 语句内时迭代。

我只需要了解为什么我的假设是错误的.. 先进的thanx

【问题讨论】:

【参考方案1】:

好的。我找到了一个非常有说服力的答案:

我开始阅读有关 MVC5 生命周期的信息,并在网上找到了很多文章。其中之一是以下链接:http://www.dotnet-tricks.com/Tutorial/mvc/TbR0041112-Asp.net-MVC-Request-Life-Cycle.html 所以我复制了图片并添加了我的评论如下[礼貌:www.dotnet-tricks.com]

然后我在另一篇文章 [ 这里:http://www.codemag.com/Article/1312081] 中读到,如何将视图呈现为字符串并将其作为操作方法的返回类型返回。这样我就可以在 using 语句中使用延迟加载并渲染视图。

所以我所做的只是对我的操作方法进行了以下更改[解释包含在 cmets 中]

 // GET: /dept/
    public string  Index()
    

        IView myView;
        string result;
        using (var ctx = new ApplicationDbContext())
        
            //my model brought using the dbContext
            IEnumerable<department> d = ctx.departments;

            // now because I want to render the View here [forcibly] and not waiting
            //for the normal MVC pipeline to render my View I had to jump to the ViewEngine
            //and ask it to render my View [while i am still inside this using statement]
            // so referring to the excellent article on :http://www.codemag.com/Article/1312081
            //I did the following:
            ControllerContext.Controller.ViewData.Model = d;
            ViewEngineResult viewEngResult = ViewEngines.Engines.FindView(ControllerContext, "~/Views/dept/Index.cshtml", null);

            myView = viewEngResult.View;
            //till this point the View is not rendered yet
            StringWriter tw = new StringWriter();// used to render the View into string
            ViewContext vc = new ViewContext(ControllerContext, myView, ControllerContext.Controller.ViewData, ControllerContext.Controller.TempData, tw);
            //it is the following method .Render(viewContext, textWriter) that will start iterating on the IEnumerable<department> object
            myView.Render(vc, tw);

            result = tw.ToString();// the rendered View is now written as string to the result
            tw.Dispose();
        

        return result;
    

我很高兴看到我的页面成功呈现,没有那个著名的处理错误;看看结果:


总结一下:

我的问题的答案是:

当您从操作方法返回 ViewResult 或 ActionResult 时;视图仍未呈现。一旦它到达 ViewEngine 的管道并且 ViewEngine 触发方法 .Render() ,此时延迟加载对象将需要 dbContext 并导致著名的 dbContext 处理错误。 我还展示了如何在操作方法本身内呈现视图。甚至在 dbContext 的 using 语句中;我可以避免那个处理错误。

谢谢大家:)

【讨论】:

【参考方案2】:

许多 LINQ 方法会对序列进行流式传输,有些方法需要对整个序列进行缓冲和操作,但如果对序列不做任何操作,它就不会存在于内存中。

【讨论】:

是的;从这个问题可以清楚地看出他意识到了这一点。问题是询问 MVC 的 View 类在什么时间点实际评估给定的序列。【参考方案3】:

IEnumerable&lt;T&gt; 是一个允许延迟执行的接口。事实上,延迟执行是 LINQ 高效的根本原因。现在,您的假设都是正确的,但是您缺少一个关键概念:.NET 中延迟执行形式的延迟加载实现。

Read my blog post on LINQ and deferred execution 了解强制枚举如何立即评估延迟代码。您的第一个示例工作的原因是因为您调用了ToList(),它强制枚举并执行代码。第二种情况下对视图对象的赋值仍然是延迟的,因为它没有通过将结果分配给View&lt;IEnumerable&lt;department&gt;&gt;来枚举

但是,您可以在 using 块内执行此操作,以强制代码在 dispose 完成之前执行:

IEnumerable<department> d = ctx.departments;

// ToList() here forces the evaluation of the deferred code
myView =  View(d.ToList());

回答您关于 MVC 管道的问题:MVC 管道不会评估您放置在 ViewResult 中的对象,除非将其适当地转换为您的视图本身使用它。因此它从不执行枚举并强制执行代码。这是你在你的视图中的电话是什么......在这种情况下,foreaching 你的Model 之类的东西。因此,当您的视图执行时,您的 using 语句早已被释放,因此延迟调用失败。

【讨论】:

这并没有回答 MVC 框架何时将 IEnumerable 评估为其值的问题;相反,它只会重复问题中的信息。 我读了你的好文章,我知道延迟执行,所以我在上面编辑了我的问题:)......所以我的问题可能是,MVC 怎么知道我将使用 [d ] 在使用语句之外的 View(d) 方法中的对象.. 我确定它是关于 MVC 内部发生的事件(例如处理控制器??)但我没有足够的经验来告诉! 是对 .ToList() 的调用决定了这一点,而不是 using 语句,尝试将其从 d 传递到 using 语句之外的 View 的示例中取出,您应该会发现相同结果。 关于 MVC 和视图,那么它将是视图中的任何内容尝试访问将触发执行的部门属性,如果它尚未被例如触发执行。 .ToList() 调用

以上是关于深入理解 MVC .net 中的延迟加载和处理错误的主要内容,如果未能解决你的问题,请参考以下文章

深入理解 ASP.NET MVC 上的 async/await

深入探讨:如何理解.Net的三层架构

ASP.NET Mvc开发之EF延迟加载

七天学会ASP.NET MVC ——深入理解ASP.NET MVC

ASP.NET MVC中的错误处理

深入理解C++中的异常处理机制