为什么嵌入在WHERE子句中的ToList的行为与在查询外部执行的ToList的行为不同?

时间:2015-06-07 11:07:55

标签: c# entity-framework

当我这样称呼时:

using (var db = new MyDbContext())
{
    var yy = db.Products.Select(xx => xx.Id);
    var items = db.Products.Where(p => yy.ToList().Contains(p.Id));
}

(请注意Where中的ToList) 生成的脚本是

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Price] AS [Price], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Products] AS [Extent1]
WHERE  EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Products] AS [Extent2]
    WHERE [Extent2].[Id] = [Extent1].[Id]
)

当我打电话给你时:

  var yy = db.Products.Select(xx => xx.Id).ToList();
  var items = db.Products.Where(p => yy.Contains(p.Id));

ToList拉出lambda)
然后生成的脚本是

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Price] AS [Price], 
[Extent1].[Name] AS [Name]
FROM [dbo].[Products] AS [Extent1]
WHERE [Extent1].[Id] IN (3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)

2 个答案:

答案 0 :(得分:3)

在第二个示例中,LINQ扩展方法ToList() 立即执行查询并返回结果列表。因此,在第二行中,有一个第二个独立的查询,该查询获取以前结果的列表。这就是您观察IN运算符的原因。

因此,在第二个示例中,您在DB上有一个查询,然后INT列表返回到您的代码,然后第二个查询在DB上运行。

在第一个示例中,第一行创建了一个查询,但尚未执行。然后在第二行,第一行的原始查询是"包裹"在另一个查询中。这正是您在生成的SQL代码中看到的:带有子查询的外部查询(来自第二行)(内部,原始,来自第一行)。

在这里,在第一个示例中,您只在DB上运行了一个查询。你没有"中间预览"受影响的ID。第二个查询中出现的ToList()实际上都包含在where子句的lambda表达式中(感谢haim770指出)和你的LINQ提供程序(1)是聪明的足以注意到ToList的左侧也是一个查询对象,因此它会跳过ToList并连接查询。在一个查询中将它全部放在一起有时/通常更好,因为数据库有时可以更好地优化它,并且这是一个查询而不是两个 - 如果您的连接速度很慢,或者如果您必须重复数百次。删除往返次数。

一切都很正常。考虑一下并习惯它,LINQ / IQueryable和类似的API是如何设计的:收集查询,允许程序员构建它,并且只在真正需要时执行它。

(1)请注意我故意说你的LINQ提供商"。这不是必须的。 LINQ提供程序获取查询的所有部分(表达式)并分析它们并转换为SQL或其他任何东西。不同的提供者(即,到不同的DB引擎,到XML,到对象等)可以不同地做,并且可以例如作为单独的查询执行ToList。同样代表"嵌入查询"在LINQ中,几乎所有人都是如此。有些提供商甚至可能会给你一个" NotSupported"当你尝试使用WHERE(即太复杂)或JOIN(只是因为心情不好)条款。严重。

答案 1 :(得分:1)

.ToList()调用实现了LinQ语句。您可以阅读它here,它被称为延期执行。

在您的第一个语句中,您将实现保留在数据库中,因此它可以形成正确的连接,因为它知道值的来源,或者 来自何处。

您的第二次尝试将值实现为您的应用程序,然后将实际值发送到数据库。所以数据库不知道它是一个加入,它只是看到数字进来。