C# 是不是;foreach“;导致Linq重复执行?
我第一次在.NET中使用实体框架,并且一直在编写LINQ查询,以便从我的模型中获取信息。我想从一开始就养成良好的编程习惯,所以我一直在研究编写这些查询的最佳方法,并得到它们的结果。不幸的是,在浏览Stack Exchange时,我似乎遇到了两种相互矛盾的解释,即延迟/立即执行如何与LINQ协同工作:C# 是不是;foreach“;导致Linq重复执行?,c#,.net,linq,C#,.net,Linq,我第一次在.NET中使用实体框架,并且一直在编写LINQ查询,以便从我的模型中获取信息。我想从一开始就养成良好的编程习惯,所以我一直在研究编写这些查询的最佳方法,并得到它们的结果。不幸的是,在浏览Stack Exchange时,我似乎遇到了两种相互矛盾的解释,即延迟/立即执行如何与LINQ协同工作: foreach导致在循环的每个迭代中执行查询: 有疑问的是,这意味着需要调用“ToList()”以立即计算查询,因为foreach正在重复计算数据源上的查询,这大大降低了操作的速度 另一个例子是
- foreach导致在循环的每个迭代中执行查询:
- foreach使查询执行一次,并且可以安全地与LINQ一起使用
Edit:除了下面公认的答案之外,我还向程序员提出了以下问题,这对我理解查询执行非常有帮助,特别是在循环过程中可能导致多个数据源命中的陷阱,我认为这将有助于其他对这个问题感兴趣的人:使用LINQ,即使没有实体,您将得到的是延迟执行是有效的。 只有通过强制迭代,才能计算实际的linq表达式。 从这个意义上讲,每次使用linq表达式时,都将对其进行求值 现在对于实体,这仍然是一样的,但是这里有更多的功能在工作。 当实体框架第一次看到这个表达式时,它会查看他是否已经执行了这个查询。如果没有,它将转到数据库并获取数据,设置其内部内存模型并将数据返回给您。如果实体框架看到它已经预先获取了数据,它就不会去数据库并使用它之前设置的内存模型将数据返回给您 这可以让你的生活更轻松,但也可能是一种痛苦。例如,如果使用linq表达式从表中请求所有记录。实体框架将加载表中的所有数据。如果以后对同一个linq表达式求值,即使当时删除或添加了记录,也会得到相同的结果 实体框架是一件复杂的事情。当然,有一些方法可以让它重新执行查询,考虑到它在自己的内存模型等方面所做的更改
我建议阅读Julia Lerman的“编程实体框架”。它解决了许多问题,比如您现在遇到的问题。通常,LINQ使用延迟执行。如果使用像
First()
和FirstOrDefault()
这样的方法,查询将立即执行。当你做这样的事情时
foreach(string s in MyObjects.Select(x => x.AStringProp))
List<string> names = People.Select(x => x.Name).ToList();
foreach (string name in names)
结果以流式方式检索,即一个接一个地检索。每次迭代器调用MoveNext
时,投影将应用于下一个对象。如果要在其中设置一个,它将首先应用过滤器,然后应用投影
如果你做了这样的事情
foreach(string s in MyObjects.Select(x => x.AStringProp))
List<string> names = People.Select(x => x.Name).ToList();
foreach (string name in names)
List Name=People.Select(x=>x.Name.ToList();
foreach(名称中的字符串名称)
那么我认为这是一个浪费的行动ToList()
将强制执行查询,枚举People
列表并应用x=>x.Name
投影。之后,您将再次枚举该列表。因此,除非您有充分的理由将数据放在列表(而不是IEnumerale)中,否则您只是在浪费CPU周期
一般来说,在使用foreach枚举的集合上使用LINQ查询不会比任何其他类似且实用的选项的性能差
另外值得注意的是,我们鼓励实现LINQ提供程序的人员像在Microsoft提供的提供程序中一样使用通用方法,但并不要求他们这样做。如果我去写一个LINQ到HTML或LINQ到我的专有数据格式提供程序,就不能保证它以这种方式运行。也许数据的性质会使立即执行成为唯一可行的选择
还有,最终编辑;如果你对乔恩·斯基特的《C#Depth》感兴趣,这本书内容丰富,读起来很棒。我的回答总结了这本书的几页(希望有合理的准确性),但如果你想了解LINQ如何在封面下工作的更多细节,这是一个很好的地方。foreach本身只运行一次数据。事实上,它只运行过一次。您不能向前或向后看,也不能用for
循环的方式更改索引
但是,如果代码中有多个foreach
s,它们都在同一个LINQ查询上运行,则可能会多次执行查询。但这完全取决于数据。如果在LINQ上迭代
First foreach starting
Doing where on 1
Doing where on 2
Foreached where on 2
Doing where on 3
Doing where on 4
Foreached where on 4
Doing where on 5
Doing where on 6
Foreached where on 6
Doing where on 7
Doing where on 8
Foreached where on 8
Doing where on 9
Doing where on 10
Foreached where on 10
First foreach ending
Second foreach starting
Doing where on 1
Doing where on 2
Foreached where on 2 for the second time.
Doing where on 3
Doing where on 4
Foreached where on 4 for the second time.
Doing where on 5
Doing where on 6
Foreached where on 6 for the second time.
Doing where on 7
Doing where on 8
Foreached where on 8 for the second time.
Doing where on 9
Doing where on 10
Foreached where on 10 for the second time.
Second foreach ending
// Main method:
static void Main(string[] args)
{
IEnumerable<int> ints = Enumerable.Range(0, 100);
var query = ints.Where(x =>
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write($"{x}**, ");
return x % 2 == 0;
});
DoForeach(query, "query");
DoForeach(query, "query.ToList()");
Console.ForegroundColor = ConsoleColor.White;
}
// DoForeach method:
private static void DoForeach(IEnumerable<int> collection, string collectionName)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\n--- {0} FOREACH BEGIN: ---", collectionName);
if (collectionName.Contains("query.ToList()"))
collection = collection.ToList();
foreach (var item in collection)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write($"{item}, ");
}
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\n--- {0} FOREACH END ---", collectionName);
}