在List上使用.Where()

时间:2014-11-09 07:56:08

标签: c# asp.net-mvc linq entity-framework linq-to-entities

假设视图中有两个可能的代码块,并使用return View(db.Products.Find(id));

之类的方式传递给它的模型
List<UserReview> reviews = Model.UserReviews.OrderByDescending(ur => ur.Date).ToList();
if (myUserReview != null)
    reviews = reviews.Where(ur => ur.Id != myUserReview.Id).ToList();

IEnumerable<UserReview> reviews = Model.UserReviews.OrderByDescending(ur => ur.Date);
if (myUserReview != null)
    reviews = reviews.Where(ur => ur.Id != myUserReview.Id);

两者之间的性能影响是什么?到目前为止,内存中的所有产品相关数据,包括其导航属性?在这个例子中使用ToList()是否重要?如果没有,是否有更好的方法在List上使用Linq查询而不必每次都调用ToList(),或者这是一种典型的方式吗?

2 个答案:

答案 0 :(得分:6)

阅读http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx

延迟执行是linq固有的众多奇迹之一。简短的版本是您的数据永远不会被触及(它在源中保持空闲,无论是在内存中,还是在数据库中,或者在任何地方)。构造linq查询时,您所做的就是创建一个“能够”枚举数据的IEnumerable类。在您实际开始枚举之前,工作才会开始,然后每个数据都从源头到管道一直到来,并且您的代码一次只能处理一个项目。如果你提前打破循环,你已经保存了一些工作 - 以后的项目永远不会被处理。这是简单的版本。

某些linq操作无法以这种方式工作。 Orderby就是最好的例子。 Orderby必须知道每一条数据,因为很可能从源中检索到的最后一块可能是你应该得到的第一块。因此,当诸如orderby之类的操作在管道中时,它实际上将在内部缓存您的数据集。因此,所有数据都已从源中提取,并且已经通过管道,直到orderby,然后orderby成为表达式中之后发生的任何操作的新临时数据源。即便如此,orderby也会尝试尽可能地遵循延迟执行范例,等待最后一刻构建其缓存。在您的查询中包含orderby仍然不会立即执行任何工作,但是一旦您开始枚举,工作就会开始。

要直接回答您的问题,您对ToList的调用正是如此。 OrderByDescending正在缓存数据源中的数据=&gt; ToList还会将其保存到您可以实际触摸的变量中(评论)=&gt;在哪里开始从评论中一次拉一条记录,如果它匹配,那么你的最终ToList调用将结果存储到内存中的另一个列表中。

除了内存含义之外,ToList还会阻止延迟执行,因为它还会在调用时停止对视图的处理,以完全处理整个linq表达式,以便构建其内存中的结果表示

如果我们谈论的记录数量达到数十个,那么这一切都不是什么大问题。你永远不会注意到运行时的差异,因为它发生得如此之快。但是在处理大规模数据集时,尽可能长时间地推迟,希望能够取消完整的枚举......除了节省内存......黄金。

在没有ToList的版本中:OrderByDescending仍将缓存通过管道处理的数据集副本,直到该点,当然在内部排序。没关系,你必须做你必须做的事情。但是,直到您实际尝试稍后在视图中检索第一条记录时才会发生这种情况。一旦缓存完成,你就得到了你的第一条记录,然后对于每一条下一条记录,你将从那个缓存中拉出来,检查where子句,你得到它,或者你不是基于那里并保存了几个记忆副本和很多工作。

奇怪的是,我敢打赌,即使你的db.Products.Find(id)引入也没有开始旋转,直到你的视图开始枚举(如果没有使用ToList)。如果db.Products是Linq2SQL数据源,那么您指定的每个其他元素都将减少为SQL语句,并且您的整个表达式将被推迟。

希望这有帮助!继续阅读延期执行。如果你想知道“如何”有效,请查看c#迭代器(yield return)。 MSDN上有一个页面,我发现它包含常见的linq操作,以及它们是否延迟执行。如果我可以跟踪它,我会更新。

/ * 编辑 * /澄清 - 以上所有内容都是在原始linq或Linq2Objects的上下文中。在我们找到该页面之前,常识会告诉您它是如何工作的。如果你闭上眼睛想象实现orderby或任何其他linq操作,如果你想不出用'yield return'来实现它的方法,或者没有缓存,那么执行就不可能延迟并且可能有缓存副本和/或完整的枚举... orderby,distinct,count,sum等... Linq2SQL是一个完整的其他蠕虫。即使在该上下文中,ToList仍将停止并处理整个表达式并存储结果,因为列表是列表,并且在内存中。但Linq2SQL能够通过将它们合并到发送到SQL服务器的生成的SQL中来推迟许多上述条款,然后是一些条款。因此,即使orderby也可以通过这种方式延迟,因为该子句将被下推到原始数据源中,然后在管道中被忽略。

祝你好运;)

答案 1 :(得分:1)

没有足够的背景知道肯定。

ToList() 保证数据已被复制到内存中,而您的第一个示例会执行两次。

第二个示例可能涉及可查询数据或其他一些按需方案。即使原始数据全部已经存储在内存中,即使您最后只在ToList()添加了一个调用,也只比第一个示例少一个内存副本。

完全有可能在第二个例子中,当你到达那个小片段的末尾时,根本没有对原始数据进行实际处理。在这种情况下,甚至在某些代码实际枚举最终reviews值之前,甚至可能不会查询数据库。

至于是否有“更好”的方式来做,不可能说。你没有定义“更好”。就个人而言,我倾向于选择第二个例子...为什么在需要之前实现数据的实现?但在某些情况下,您想要强制实现。这取决于场景。