LINQ 实体 Where 子句不在正确位置
Posted
技术标签:
【中文标题】LINQ 实体 Where 子句不在正确位置【英文标题】:LINQ Entities Where clause not in correct place 【发布时间】:2018-02-02 15:39:35 【问题描述】:显然我错过了 LINQ to entity 的工作原理。希望你们中的一个可以教育我。
请在本地尝试以下操作,如果您看到相同的结果,请告诉我。这里真的很奇怪……
让我们看一个使用导航属性的非常简单的 LINQ 表达式。
这是在 LinqPad 中的 C# 语句中生成的。
var result = (from ge in group_execution
where ge.automation_sequences.project.client_id == 1 && ge.parent_group_exec_id != null
select new
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
);
result.Dump();
或者,我们可以使用连接...这将导致同样糟糕的结果,但让我们继续...
var result = (from ge in group_execution
join aseq in automation_sequences on ge.automation_sequence_id equals aseq.id
join p in project on aseq.project_id equals p.id
where p.client_id == 1 && ge.parent_group_exec_id != null
select new
ge.id,
ge.parent_group_exec_id,
p.client_id
);
result.Dump();
这些非常简单的 LINQ 表达式生成以下 SQL:
SELECT
[Filter1].[id1] AS [id],
[Filter1].[parent_group_exec_id] AS [parent_group_exec_id],
[Extent5].[client_id] AS [client_id]
FROM (SELECT [Extent1].[id] AS [id1], [Extent1].[automation_sequence_id] AS [automation_sequence_id], [Extent1].[parent_group_exec_id] AS [parent_group_exec_id]
FROM [dbo].[group_execution] AS [Extent1]
INNER JOIN [dbo].[automation_sequences] AS [Extent2] ON [Extent1].[automation_sequence_id] = [Extent2].[id]
INNER JOIN [dbo].[project] AS [Extent3] ON [Extent2].[project_id] = [Extent3].[id]
WHERE ([Extent1].[parent_group_exec_id] IS NOT NULL) AND (1 = [Extent3].[client_id]) ) AS [Filter1]
LEFT OUTER JOIN [dbo].[automation_sequences] AS [Extent4] ON [Filter1].[automation_sequence_id] = [Extent4].[id]
LEFT OUTER JOIN [dbo].[project] AS [Extent5] ON [Extent4].[project_id] = [Extent5].[id]
这让我很困惑。对于我的生活,我无法理解为什么 LINQ 会这样做。太可怕了,看看执行计划:
现在让我们在 SSMS 中手动清理它并查看正确的 SQL 和执行计划:
好多了,但是我们如何让 LINQ 以这种方式运行?
还有人看到这个吗?有没有其他人看到过这个并纠正过它,如果有,如何纠正?
感谢您对此进行调查。
更新,正在尝试修复 Chris Schaller:
var result = (from ge in group_execution
select new
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
).Where(x=>x.client_id == 1 && x.parent_group_exec_id != null);
result.Dump();
让你们都知道我正在通过 SQL Server Profiler 监控 SQL。如果有人知道这样做有任何问题,请告诉我。
更新,一个 JOINS 的修复,但不是导航属性,一个原因,但是为什么?
这是您的解决方案:
var result = (from ge in group_execution.Where(x=>x.parent_group_exec_id != null)
join aseq in automation_sequences on ge.automation_sequence_id equals aseq.id
join p in project on aseq.project_id equals p.id
where p.client_id == 1// && ge.parent_group_exec_id != null
select new
ge.id,
ge.parent_group_exec_id,
p.client_id
);
result.Dump();
Null 检查不应导致框架像这样混乱。为什么我必须这样写?这对我来说似乎是框架中的一个缺陷。它会使我的动态表达式更难写,但也许我能找到一种方法。
导航属性仍然一团糟……所以我仍然很伤心。下图:
var result = (from ge in group_execution.Where(x=>x.parent_group_exec_id != null)
where ge.automation_sequences.project.client_id == 1// && ge.parent_group_exec_id != null
select new
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
);
result.Dump();
【问题讨论】:
顺便说一句,这些优化是 SQL 查询优化器/执行计划生成器的责任。 EF(以及任何开发人员)应提供有效 SQL 语句。那条 SQL 语句怎么不重要 - 现在 RBO 已经不复存在了。 尝试着眼于实际问题。此查询形状是否会给您带来任何问题 w.r.t.表现?众所周知,EF(或任何 ORM)不会产生人类会仔细构建的查询。但通常他们做得不错。此外,EF 始终致力于更好地生成查询,因此在下一个版本中,您自己的优化可能会变得无用(甚至可能产生不利影响)。 【参考方案1】:在定义了 select 语句的结构之后将 where 子句移到
var result = (from ge in group_execution
select new
ge.id,
ge.parent_group_exec_id,
ge.automation_sequences.project.client_id
).Where(x => x.client_id == 1 && x.parent_group_exec_id != null)
result.Dump();
【讨论】:
不走运,我已经用这种方法的结果更新了原始帖子 为什么这会对生成的 SQL 产生影响? 我明白了,实际上新的执行计划效率更高,它在内部查询中正确过滤了父组,从而减少了对将被过滤掉的记录的不必要的索引搜索。不幸的是,如果不自己指定选择,您将不会得到比这更清晰的输出。解析器分离范围的方式是我们在使用 Linq-to-entities 时接受的一种必要的邪恶。重要的是现在的执行计划应该是接近的,额外的试图在范围之间加入内存中的数据不应该给运行时增加可测量的开销。 @DavidG 你在开玩笑吗? OP 非常友好地展示了这对生成的 SQL 的影响。谢谢 spyder1329 ;)【参考方案2】:请记住,Linq-to-entities 将查询结果扁平化以作为 SQL 执行,然后根据这些结果对对象图进行水合。
当您的查询使用导航属性或连接时,查询解析器必须允许这些子查询(Extents)的结果为零,以确保输出中所需的所有列和表示任何中间处理。通过在查询的早期为 != null 显式指定表上的过滤器,解析器知道该字段和由该字段链接的任何关系都不可能为空,直到那时解析器准备查询,就好像连接将返回空结果
值得检查,但我想知道 UseDatabaseNullSemantics 是否与此有关?
试试:
dbContext.Configuration.UseDatabaseNullSemantics = false
在 Linq 中,我们可以随意指定 Where 子句,改进生成的 SQL,我们应该在查询的早期和细粒度地过滤。
解析器引擎经过优化,可以按顺序跟踪和执行您的查询,并在查询结束时生成良好的 SQL。不要尝试以与构建 SQL 相同的方式编写 linq-to-entities,我知道这是违反直觉的,因为语法相似
一个好的技术是假设在每个子句之前,之前语句中的所有记录都已加载到内存中,并且下一个操作将影响所有这些记录。因此,您希望通过在进入下一个子句之前指定一个过滤器来减少每个附加操作之前的记录
一般来说,如果你有一个基于根表的过滤条件,在定义所有其他连接和过滤器甚至选择之前将其应用于查询,你会得到更干净的 sql。
【讨论】:
感谢您的评论。我将在原始帖子中包含我所追求的真正最终结果。我基本上在 where 子句中有一个可选的 EXISTS 语句。该语句需要引用原始 select 语句中的一个字段才能运行得更快,但由于 LINQ 正在创建这些新的 WHERE 子句,因此我很难控制其行为。以上是关于LINQ 实体 Where 子句不在正确位置的主要内容,如果未能解决你的问题,请参考以下文章
使用 Func<T, string> lambda 动态构造 where 子句 - linq 到实体