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的值在第一个查询中是硬编码值,在第二个查询中是一个变量(这在世界上很有意义)。我的问题是关于为什么变量版本会导致额外的子查询。

谢谢!

EF 4.1:为什么将常量转换为变量会导致额外的子查询

答案相当简单。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或存储过程。