深入理解 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<T>
是一个允许延迟执行的接口。事实上,延迟执行是 LINQ 高效的根本原因。现在,您的假设都是正确的,但是您缺少一个关键概念:.NET 中延迟执行形式的延迟加载实现。
Read my blog post on LINQ and deferred execution 了解强制枚举如何立即评估延迟代码。您的第一个示例工作的原因是因为您调用了ToList()
,它强制枚举并执行代码。第二种情况下对视图对象的赋值仍然是延迟的,因为它没有通过将结果分配给View<IEnumerable<department>>
来枚举
但是,您可以在 using 块内执行此操作,以强制代码在 dispose 完成之前执行:
IEnumerable<department> d = ctx.departments;
// ToList() here forces the evaluation of the deferred code
myView = View(d.ToList());
回答您关于 MVC 管道的问题:MVC 管道不会评估您放置在 ViewResult
中的对象,除非将其适当地转换为您的视图本身使用它。因此它从不执行枚举并强制执行代码。这是你在你的视图中的电话是什么......在这种情况下,foreach
ing 你的Model
之类的东西。因此,当您的视图执行时,您的 using
语句早已被释放,因此延迟调用失败。
【讨论】:
这并没有回答 MVC 框架何时将IEnumerable
评估为其值的问题;相反,它只会重复问题中的信息。
我读了你的好文章,我知道延迟执行,所以我在上面编辑了我的问题:)......所以我的问题可能是,MVC 怎么知道我将使用 [d ] 在使用语句之外的 View(d) 方法中的对象.. 我确定它是关于 MVC 内部发生的事件(例如处理控制器??)但我没有足够的经验来告诉!
是对 .ToList() 的调用决定了这一点,而不是 using 语句,尝试将其从 d 传递到 using 语句之外的 View 的示例中取出,您应该会发现相同结果。
关于 MVC 和视图,那么它将是视图中的任何内容尝试访问将触发执行的部门属性,如果它尚未被例如触发执行。 .ToList() 调用以上是关于深入理解 MVC .net 中的延迟加载和处理错误的主要内容,如果未能解决你的问题,请参考以下文章
深入理解 ASP.NET MVC 上的 async/await