EF 4.1: Почему превращение константы в переменную приводит к дополнительному подзапросу? - PullRequest
4 голосов
/ 07 мая 2011

Сегодня я обнаружил, что Entity Framework добавляет ненужный подзапрос к генерируемому SQL. Я начал копать свой код, пытаясь выяснить, откуда он может взяться. А (долго), а потом я точно определил, что вызывает это. Но теперь я более запутан, чем когда я начинал, так как понятия не имею, почему это вызывает это.

По сути, я обнаружил, что в определенных сценариях простое преобразование константы в переменную может изменить SQL, который генерирует Entity Framework. Я сократил все до минимума и упаковал его в небольшое консольное приложение:

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 ]

4 голосов
/ 07 мая 2011

Ответ довольно прост. Ваш запрос LINQ выражается деревьями выражений. Разница с константной переменной от неконстантной заключается в ConstantExpression и ParameterExpression .

Когда вы используете const, ваш запрос LINQ использует ConstExpression для этой переменной, а когда вы используете не const, он использует ParameterExpression, которые интерпретируются EF Runtime по-разному.

Константа фактически означает, что значение никогда не изменится, и значение может быть встроено в запрос.

3 голосов
/ 07 мая 2011

НЕ ответ на вопрос - просто контекст использования параметров.

Это связано с созданием запроса, который будет повторно использовать существующие планы запросов.

Если вы введете переменную (в отличие от ссылки на параметр) в генерируемый SQL, то SQL Server (и, возможно, другие механизмы базы данных) не смогут повторно использовать тот же план при изменении переменной.

Для констант это не проблема, потому что вы знаете, что значение всегда одинаково, но для переменных каждый раз, когда запрос выполняется SQL, и, следовательно, план запроса будет немного отличаться.

Это может показаться не так много, но SQL имеет только определенное количество пространства, выделенного для планов запросов, поэтому наличие сотен / тысяч незначительных изменений в кеше - это, так сказать, настоящая «трата пространства»!

1 голос
/ 07 мая 2011

Как говорили люди. Разница между обоими запросами минимальна.

Причина в том, что выражение, которое создается при создании LINQ, отличается при использовании переменной и константы. И EF поймает это и сгенерирует ваш SQL соответственно. Он знает, что он никогда не изменится, поэтому его можно жестко закодировать в запросе (возможного) повышения производительности.

Редактировать : Я не думаю, что есть ответ на этот вопрос, кроме «Вот как это делает EF». Но очень хорошо известно, что EF любит создавать множество подвыборов. Это может привести ко многим подвыборам для более сложных запросов. Некоторые даже отклоняют даже использование EF для этого факта. Но это просто цена за использование инструмента, такого как EF. Вы теряете мелкозернистый контроль над чем-то, что может значительно повысить производительность. Почему вы используете .NET, когда вы можете использовать C и повысить производительность? Зачем использовать C, когда вы можете использовать сборку, чтобы получить больше прирост производительности?

Единственный способ быть безопасным и при этом иметь возможность использовать слой высокой абстракции EF - это часто использовать SQL profiller и проверять, нет ли запросов, которые занимают слишком много времени для реальных данных. А если вы найдете некоторые из них, то либо конвертируйте их в прямой SQL, либо в хранимые процедуры.

1 голос
/ 07 мая 2011

Это большая разница в SQL? Внутренний запрос такой же, как и исходный запрос, а внешний запрос - это просто оболочка над внутренним, которая не меняет результирующий набор.

Если только это не вызывает проблем, я лично не стал бы беспокоиться об этом. Различаются ли планы запросов между двумя разновидностями запроса? Я предполагаю, что они идентичны.

...