给定一个返回MVC视图的非常基本的LINQ,延迟执行在什么时候开始执行?
在控制器中:
public ActionResult Index()
{
var model = _fooService.GetAll();
return View(model);
}
在模型中:
@foreach (var item in Model) {
<tr>
<td>@item.Bar</td>
</tr>
}
当我们调用_fooService.GetAll()
时,查询不会被执行,但是会延迟到稍后的某个时间点 - 但是它执行的确切点是什么?
return View(model);
语句(看起来不像)?@foreach (var item in Model)
行?@item.Bar
行了吗?return View(model);
与正在呈现的视图之间发生了什么?答案 0 :(得分:5)
查询执行(我假设GetAll返回iqueryable)将被延迟到视图中,并将在foreach行上解包,因为这是您开始迭代集合的第一个地方。
<强>更新强> 对于任何感兴趣的人,这里是“证明”。创建一个这样的类:
public class DiagnosticCollection<T> : System.Collections.Generic.List<T>
{
public new Enumerator GetEnumerator()
{
Debug.Print("Collection Unwrap");
return base.GetEnumerator();
}
}
在你的视图中测试它:
@model PlayMvc.Models.DiagnosticCollection<string>
@{System.Diagnostics.Debug.Print("Before foreach");}
@foreach (var item in Model)
{
System.Diagnostics.Debug.Print("After foreach");
@item
}
答案 1 :(得分:3)
MSDN documentation在延迟查询执行部分(强调我的)下解决了这个问题。
在返回值序列的查询中,查询变量 本身永远不会保存查询结果,只存储查询 命令。查询的执行延迟直到查询变量 在foreach或For Each循环中迭代 ...
缩小了选项2和3的答案。
foreach
只是语法糖,在编译器下面重写为while循环。对here发生的事情有一个非常彻底的解释。基本上你的循环最终会看起来像这样
{
IEnumerator<?> e = ((IEnumerable<?>)Model).GetEnumerator();
try
{
int m; // this is inside the loop in C# 5
while(e.MoveNext())
{
m = (?)e.Current;
// your code goes here
}
}
finally
{
if (e != null) ((IDisposable)e).Dispose();
}
}
枚举器在它到达循环内部的代码之前是先进的,所以在你到达@item.Bar
之前稍微。这只留下选项2,@foreach (var item in Model)
行(虽然在编译器完成代码后技术上该行不存在)。
如果查询将在调用GetEnumerator()
或第一次调用e.MoveNext()
时执行,我就不会起诉。
正如@pst在评论中指出的那样,还有其他方法可以触发执行查询,例如通过调用ToList
,它可能不会在内部使用foreach
循环。 MSDN文档对此here:
IQueryable接口继承IEnumerable接口,以便if 它表示一个查询,可以枚举该查询的结果。 枚举导致与IQueryable关联的表达式树 要执行的对象。“执行表达式”的定义 tree“特定于查询提供程序。例如,它可能涉及 将表达式树翻译成适当的查询语言 基础数据源。 不返回可枚举的查询 调用Execute方法时执行结果。
我对此的理解是尝试枚举表达式将导致它执行(通过foreach
或其他方式)。究竟如何发生将取决于提供商的实施。
答案 2 :(得分:2)
作为澄清的一点,LINQ to SQL / Entity Framework在调用GetEnumerator时评估LINQ查询表达式。这在foreach的封面下使用,因此虽然延迟执行似乎等到foreach发生,但它实际上是foreach使用的GetEnumerator,它确实是关键的执行点。
答案 3 :(得分:-2)
工作推迟到需要完成。在您的示例中,查询将在尝试获取item
的值时运行。