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

C# 为什么嵌入WHERE子句的ToList与在查询外部执行的ToList的行为不同?,c#,entity-framework,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)); } var yy = db.Products.Select(xx => xx.Id).ToList(); var items = db.Products.W

当我称之为:

using (var db = new MyDbContext())
{
    var yy = db.Products.Select(xx => xx.Id);
    var items = db.Products.Where(p => yy.ToList().Contains(p.Id));
}
  var yy = db.Products.Select(xx => xx.Id).ToList();
  var items = db.Products.Where(p => yy.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]
)
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)
当我称之为:

using (var db = new MyDbContext())
{
    var yy = db.Products.Select(xx => xx.Id);
    var items = db.Products.Where(p => yy.ToList().Contains(p.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  EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Products] AS [Extent2]
    WHERE [Extent2].[Id] = [Extent1].[Id]
)
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)
.ToList()
调用具体化LinQ语句。你可以读到它,它叫做延迟执行

在第一个语句中,将具体化留给数据库,这样它就可以形成一个适当的连接,因为它知道值来自何处,或者更确切地说,将来自何处


第二次尝试将值具体化到应用程序,然后将实际值发送到数据库。所以数据库不知道它是一个连接,它只看到数字输入

在第二个示例中,LINQ扩展方法
ToList()
立即执行查询并返回结果列表。因此,在第二行中,有一个第二个独立的查询,它获取先前结果的列表。这就是为什么要在操作符中观察

因此,在第二个示例中,您对DB进行了一次查询,然后INT列表飞回您的代码,然后第二次查询在DB上运行

在第一个示例中,第一行创建了一个查询,但还没有执行它。然后在第二行中,来自第一行的原始查询被“包装”到另一个查询中。这正是您在生成的SQL代码中看到的:外部查询(从第二行开始)和子查询(内部、原始、从第一行开始)

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

一切正常。仔细考虑并习惯它,这就是LINQ/IQueryable和类似API的设计目的:收集查询,允许程序员构建查询,并仅在真正需要时执行查询


(1) 请注意,我故意说“您的LINQ提供者”。这不是必须的。LINQ提供者获取查询的所有部分(表达式),并对它们进行分析,然后转换为SQL或其他任何形式。不同的提供者(即,不同的DB引擎、XML、对象等)可以以不同的方式执行,例如,可以将ToList作为单独的查询执行。同样的意思是“嵌入查询”,同样的意思是LINQ中的几乎所有查询。。当您尝试使用WHERE(即过于复杂)或JOIN(仅仅因为心情不好)子句时,某些提供者甚至可能会抛出一个“NotSupported”(不受支持)。说真的。

似乎是合法的。您认为它有什么奇怪之处?var yy=db.Products.Select(xx=>xx.Id)之间有什么不同之处;yy.ToList()和db.Products.Select(xx=>xx.Id).ToList()@M.Azad:区别在于lambda处理的类型。第一个函数为您创建一个
yy
查询(IQueryable),您正在对其调用一个ToList==立即执行。你现在在评论中写的第二个是完全相同的。第三个您没有注意到的是:
db.Foobar.Where(f=>db.Products.Where(…).ToList())
。这里不执行ToList。此外,该行的任何部分都没有真正执行。它与整个表达式
f=>db.Products.Where(…).ToList()
一起被视为
expression
,并被转换为Foobar集的Where谓词。目标是获取SQL查询。引擎检查每个子句,查看
f=>db.Products.Where(…).ToList()
表达式,并尝试从中生成一个sql Where。它注意到内部有一个“查询”,并生成这样一个子查询。误导性的一点是,如果您不习惯LINQ的工作方式,您可能会认为
f=>db.Products.Where(…).ToList()
实际上试图为每个或某些Foobar执行
db.Products.Where(…).ToList()
部分。这对于LINQ to对象来说是绝对正确的,但对于LINQ to SQL、LINQ to EF等来说,这并不是必需的。这里的关键部分是,由于
ToList()
调用封装在
表达式中,EF能够忽略它,而只从中提取“is contains”逻辑。@haim770:是的,我忽略了这个。非常感谢
将具体化留给数据库
——而不是数据库。到(在本例中)生成SQL的LINQ提供程序。DB在这里根本看不到“第一个查询”或“具体化”。这是提供商的一些智能优化。有一点误导,因为ToList/ToArray/ToEnumerable传统上意味着立即执行它,所以我的第一个盲目猜测是我和II将导致相同的查询。