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将导致相同的查询。