EF 4.1:为什么在常量子查询中将常量变为变量?

时间:2011-05-07 15:55:15

标签: c# entity-framework entity-framework-4 linq-to-entities code-first

今天我发现Entity Framework正在为它生成的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在第一个查询中是一个硬编码值而在第二个查询中是一个变量(这在世界上是有意义的)。我的问题是为什么变量版本会导致额外的子查询。

谢谢!

4 个答案:

答案 0 :(得分:4)

答案很简单。您的LINQ查询用表达式树表示。 const变量与非const变量的区别在于ConstantExpressionParameterExpression

使用const时,LINQ查询对此变量使用ConstExpression,当使用非const时,它使用ParameterExpression,EF Runtime会对其进行不同的解释。

常量实际上意味着该值永远不会更改,并且值可以内联到查询中。

答案 1 :(得分:3)

不是问题的答案 - 仅使用参数的上下文。

这与创建查询有关,它将重用现有的查询计划。

如果将变量(而不是对参数的引用)注入到生成SQL中,那么当变量发生变化时,SQL Server(可能还有其他数据库引擎)将无法重用相同的计划。 / p>

对于常量,这不是问题,因为您知道值总是相同的,但是对于每次执行查询时的变量,SQL和查询计划会略有不同。

这可能听起来不是很多,但SQL只为查询计划分配了一定的空间,因此在缓存中有数百/数千个微小变化可以说是真正的“浪费空间”!

答案 2 :(得分:1)

这实际上是SQL的一大不同吗?内部查询与原始查询相同,外部查询只是内部的包装器,不会更改结果集。

除非这会引起问题,否则我个人不会担心。两种查询的查询计划有何不同?我猜他们是完全相同的。

答案 3 :(得分:1)

像人们说的那样。两个查询之间的差异很小。

原因是当您使用变量和常量时创建LINQ时创建的表达式是不同的。 EF会抓住这个并且会依次生成你的SQL。它知道它永远不会改变,所以它可以被硬编码到查询中以获得(可能的)性能增益。

修改: 我不认为这个问题有答案,除了“那就是EF如何做到这一点。”但众所周知,EF喜欢创建许多子选择。对于更复杂的查询,它可以导致许多子选择。甚至有些人甚至因为这个事实而使用EF。但这只是使用像EF这样的工具的价格。你对某些东西进行了细致的控制,这可以带来很大的性能提升。为什么使用.NET,何时可以使用C并获得更高的性能?为什么在使用汇编时使用C来获得更高的性能?

只有安全且仍然能够使用高抽象层EF的方法是经常使用SQL profiller并检查是否存在对实际数据花费太长时间的查询。如果你找到一些,那么要么将它们转换为直接SQL或存储过程。