EF 4.1:为什么将常量转换为变量会导致额外的子查询
本文关键字:查询 变量 为什么 转换 常量 EF | 更新日期: 2023-09-27 17:55:10
今天我发现实体框架正在向它生成的SQL添加不必要的子查询。我开始挖掘我的代码,试图缩小它可能来自哪里。一(长)过了一会儿,我指出了导致它的原因。但是现在我比刚开始的时候更困惑,因为我不知道为什么它会导致它。
基本上,我发现在某些情况下,简单地将常量转换为变量可以更改实体框架生成的SQL。我已经将所有内容缩小到最低限度,并将其打包在一个小控制台应用程序中:
using System;
using System.Data.Entity;
using System.Linq;
class Program
{
private static readonly BlogContext _db = new BlogContext();
static void Main(string[] args)
{
const string email = "foo@bar.com";
var comments = from c in _db.Comments
where c.Email == email
select c;
var result = (from p in _db.Posts
join c in comments on p.PostId equals c.PostId
orderby p.Title
select new { p.Title, c.Content });
Console.WriteLine(result);
}
}
public class BlogContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public int PostId { get; set; }
public string Email { get; set; }
public string Content { get; set; }
}
这显示了以下输出,这是完美的:
SELECT
[Extent1].[PostId] AS [PostId],
[Extent1].[Title] AS [Title],
[Extent2].[Content] AS [Content]
FROM [dbo].[Posts] AS [Extent1]
INNER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
WHERE N'foo@bar.com' = [Extent2].[Email]
ORDER BY [Extent1].[Title] ASC
现在,如果我email
变量
/*const*/ string email = "foo@bar.com";
输出发生了根本性的变化:
SELECT
[Project1].[PostId] AS [PostId],
[Project1].[Title] AS [Title],
[Project1].[Content] AS [Content]
FROM ( SELECT
[Extent1].[PostId] AS [PostId],
[Extent1].[Title] AS [Title],
[Extent2].[Content] AS [Content]
FROM [dbo].[Posts] AS [Extent1]
INNER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
WHERE [Extent2].[Email] = @p__linq__0
) AS [Project1]
ORDER BY [Project1].[Title] ASC
作为旁注,LINQ to SQL 似乎没有这样做。我知道忽略这一点可能是可以的,因为两个命令都返回相同的数据。但我非常好奇为什么会这样。直到今天,我总是有一种(也许是错误的?)印象,即将常量转换为变量总是安全的,只要值保持不变(在这种情况下确实如此)。所以我不得不问...
为什么看似微不足道的更改会导致生成的 SQL 出现如此大的差异?
更新:
需要明确的是,我的问题不是关于email
的值在第一个查询中是硬编码值,在第二个查询中是一个变量(这在世界上很有意义)。我的问题是关于为什么变量版本会导致额外的子查询。
谢谢!
答案相当简单。LINQ 查询使用表达式树表示。常量变量与非常量变量的区别在于常量表达式和参数表达式。
使用 const 时,LINQ 查询对此变量使用 ConstExpression
,使用非 const 时,它使用 EF 运行时以不同方式解释的ParameterExpression
。
常量实际上意味着值永远不会更改,并且该值可以内联到查询中。
问题的答案 - 只是使用参数的上下文。
这与创建查询有关,以便它将重用现有查询计划。
如果将变量(而不是对参数的引用)注入到生成的 SQL 中,则当变量更改时,SQL Server(可能还有其他数据库引擎)将无法重用相同的计划。
对于常量,这不是问题,因为您知道值总是相同的,但是对于变量,每次执行查询时,SQL和查询计划都会略有不同。
这听起来可能不多,但SQL只为查询计划分配了一定数量的空间,因此缓存中有数百/数千个微小的变化可以说是真正的"浪费空间"!
这实际上是 SQL 中的大区别吗? 内部查询与原始查询相同,外部查询只是内部查询的包装器,不会更改结果集。
除非这会引起问题,否则我个人不会担心。 两种查询类型的查询计划是否不同? 我的猜测是它们是相同的。
就像人们说的那样。两个查询之间的差异很小。
原因是创建 LINQ 时创建的表达式在使用变量和常量时不同。EF 将捕获这一点,并相应地生成您的 SQL。它知道它永远不会改变,因此可以将其硬编码到查询中以获得(可能的)性能提升。
编辑:我认为除了"EF就是这样做的"之外,这个问题没有答案。但众所周知,EF 喜欢创建许多子选择。对于更复杂的查询,它可能会导致许多子选择。有些人甚至为此而使用 EF 而消失。但这只是使用 EF 等工具的代价。你失去了对某些东西的细粒度控制,这可以带来巨大的性能提升。当您可以使用 C 并获得更多性能时,为什么要使用 .NET?当您可以使用汇编时,为什么要使用 C 来获得更多的性能提升?
安全且仍然能够使用高抽象层 EF 的唯一方法是经常使用 SQL 填充器,并检查是否存在对实际数据花费太长时间的查询。如果你找到一些,那么要么将它们转换为直接SQL或存储过程。