EF 4.1: Почему превращение константы в переменную приводит к дополнительному подзапросу?

Сегодня я обнаружил, что 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 = "[email protected]";

        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'[email protected]' = [Extent2].[Email]
ORDER BY [Extent1].[Title] ASC

Теперь, если я сделаю email переменной:

/*const*/ string email = "[email protected]";

Вывод кардинально меняется:

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 является жестко запрограммированным значением в первом запросе и переменной во втором (что имеет смысл). Мой вопрос о том, почему версия переменной приводит к дополнительному подзапросу.

Спасибо!


person Daniel Liuzzi    schedule 07.05.2011    source источник
comment
Я бы не стал беспокоиться о форме запроса - SQL предназначен для выражения желаемых результатов, а не процесса их достижения - и я был бы удивлен, если бы было много или любая разница в плане запроса. Оптимизатор должен создать почти такой же план запроса.   -  person Damien_The_Unbeliever    schedule 07.05.2011
comment
Это увлекательное открытие!! Проголосовал только за это!!   -  person John Zabroski    schedule 18.04.2015


Ответы (4)


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

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

person Will A    schedule 07.05.2011
comment
Спасибо Уилл! Ну, в этом контексте разница, вероятно, незначительна. Но на самом деле код, над которым я работаю, — это уровень доступа к данным намного более крупного приложения, которое использует IQueryables повсюду. Я просто боюсь, что если небольшое изменение, подобное этому, приведет к созданию дополнительного запроса, то, возможно, более серьезные изменения в конечном итоге приведут к какой-то неприятной беспорядку T-SQL в будущем. Основная причина, по которой я забочусь об этом, заключается в том, что я люблю использовать LINQ и планирую продолжать использовать его исключительно (без необходимости прибегать к sprocs, когда что-то запутается). - person Daniel Liuzzi; 07.05.2011
comment
@Daniel - на самом деле вы не получаете здесь дополнительный запрос - у вас все еще есть только один запрос, хотя и немного более сложный запрос. У SQL Server не будет проблем с оптимизацией внешней части запроса — честное слово! - person Will A; 07.05.2011
comment
Да, я знаю, что БД попала только один раз, и да, я действительно хочу верить, что SQL Server достаточно умен, чтобы понять, что внешний запрос является избыточным. Я просто не мог удержаться, и мне нужно было спросить и убедиться :) - person Daniel Liuzzi; 07.05.2011
comment
@Daniel - спросить никогда не помешает, и продолжайте спрашивать - сомневайтесь во всем, ничему не доверяйте !!!! :) - person Will A; 07.05.2011

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

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

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

person Ivan Zlatanov    schedule 07.05.2011
comment
И это напоминает мне, что мне еще многое предстоит узнать о LINQ в целом и деревьях выражений в частности. Большое спасибо за ссылки, Иван. - person Daniel Liuzzi; 07.05.2011
comment
@Euphoric: трудно понизить ответ, который казался правильным до того, как вопрос (после) был отредактирован - person BrokenGlass; 07.05.2011
comment
Это, наверное, действительно важно. Если вы скажете, что это константа, а это не так, то он будет каждый раз генерировать и кэшировать новый запрос на SQL-сервере. С другой стороны, если это переменная, сам запрос может быть одним и тем же, несмотря на разные значения переменной, поэтому он будет сгенерирован и кэширован только один раз. Итак... если ЗАПРОС будет выполняться с разными значениями с течением времени, оставьте его переменной. Если это действительно константа в вашем приложении, используйте константу. Простой. - person Triynko; 13.07.2017

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

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

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

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

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

person Alex James    schedule 07.05.2011
comment
+1 Думаю, это ответ на вопрос. Более того, это выглядит как очень понятное объяснение одной из самых больших загадок EF. - person Ladislav Mrnka; 08.05.2011

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

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

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

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

person Euphoric    schedule 07.05.2011
comment
Спасибо Эйфорик. Да, я заметил, что значение жестко запрограммировано при использовании константы. Это я понимаю, и это имеет смысл. Мой вопрос был о подзапросе. - person Daniel Liuzzi; 07.05.2011