如何确定哪些Linq函数不会多次调用SQL?

4
            var costCenters = from c in dbContext.CostCenters //no sql call here
                          orderby c.DisplayRank descending
                          select c;


            List<CostCenter> lstCostCenter = costCenters.ToList();//Immediate execution to sql the first time

            lstCostCenter = costCenters.ToList();//no Sql call ??

            int test = costCenters.Count();//Call Sql everytime
            test = costCenters.Count();//Call Sql again???

我正在使用Entity Framework 5。

我开始学习Linq。 我真的很困惑哪些立即执行函数每次都会调用SQL。 如您在上面的示例中所看到的,ToList()和Count()均为立即执行函数,但仅Count()将在后续调用时连接到sql。 ToList()只连接到sql一次,但Count()每次都会连接到Sql。
如何确定哪些linq函数不会多次调用sql?

3个回答

4

首先,ToList()永远不会使用延迟执行。它总是立即实现信息。它不会每次都返回到数据库,因为它已经抓取了所有实体。

要了解哪些运算符做什么,只需查看此链接


从你给我的链接来看,ToList()和Count()都被归类为立即执行。根据网站的定义,立即执行意味着数据源在查询声明的代码点进行读取和操作。所有返回单个非可枚举结果的标准查询运算符都会立即执行。因此,无论何时调用ToList()和Count(),它们都应该每次都会调用SQL对吗? - gavin
不是ToList,因为它只需要获取所有记录本身。一旦所有记录都被加载,EF就知道不需要返回SQL,因为查询已经被加载了。 - Corey Adler
但是,如果 EF 不需要为第二个 ToList 回调 SQL,那么为什么它需要为第二个 Count 回调 SQL 呢? - Rawling
1
感谢您指出它们是立即执行的,我终于能够通过谷歌找到答案了。区别在于转换运算符(如tolist()或toarray())会复制结果并允许您根据此链接(http://www.dotnetcurry.com/ShowArticle.aspx?ID=750)多次访问它。 - gavin

3
第一次调用ToList()的区别在于它将IQueryable(仅是查询的定义)转换为IEnumerable(查询数据库)的列表。换句话说,您会得到一个现在在内存中的对象列表,因此对结果列表进行的任何进一步的LINQ调用都使用与内存中的对象一起工作的IEnumerable版本。此外,EF具有缓存结果的功能,因此即使您在原始IQueryable引用上调用ToList,它也很可能使用内存中的对象而不是从数据库获取它们。我猜测Count再次命中数据库,而不是计算缓存的结果,因为计数的查询与ToList的查询不同(它是一种聚合/分组类型),而且也许是因为DB引擎更有效地提供计数。
在您的示例中,costCenters是一个IQueryable,因为您只是定义了一个查询,但尚未调用ToList。lstCostCenter是IEnumerable,表示经过执行ToList后的查询的内存中结果。通常,立即生成结果的调用(例如.Count、.ToList等)在IQueryable上执行时会导致DB调用(异常情况下会找到可重用的缓存结果),而在IEnumerable对象上(在您的情况下是lstCostCenter)的调用将在内存中操作。
为了获得更可预测的结果,请先在IQueryable上调用ToList,并在IEnumerable上进行所有进一步的调用。换句话说,您在lstCostCenter上调用的任何内容都保证不会命中数据库。这通常是处理它的最佳方法,除非您期望结果列表很大。例如,如果lstCostCenter最终有10,000个对象,那么您可能不想执行lstCostCenter.Where(x=>x.Blah > 5),因为这将循环遍历所有10,000个内存中的对象进行过滤。在这种情况下,更好的方法是首先修改查询并附加对IQueryable的附加调用,然后调用ToList,以便利用更适合处理大型集合的DB引擎:costCenters.Where(x=>x.Blah > 5).ToList()

经过进一步阅读,我意识到缓存是通过“强制执行”来完成的。 - gavin

1

http://msdn.microsoft.com/en-us/library/bb738633.aspx

区别在于“强制执行”。如果您强制立即执行查询,则会缓存结果。转换运算符如ToList()、ToArray()甚至foreach都被归类为强制执行,因此进一步调用将仅在内存缓存上操作。而Count()、First()、Max()和Average()不被视为强制执行...它们是查询的一部分...我猜。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接