Проблема с Entity Framework Linq Query: выполняется мгновенно в SSMS и 8-10 в EF LINQ

Мне был задан следующий запрос в SQL (имена переменных obfuscated), который пытается получить значения (Ch, Wa, Bu, Hi), что приводит к наибольшему числу (cnt) записей Pi.

select top 1 Pi.Ch, Pi.Wa, Pi.Bu, Pi.Hi, COUNT(1) as cnt 
from Product, Si, Pi
where Product.Id = Si.ProductId
and Si.Id = Pi.SiId
and Product.Code = @CodeParameter
group by Pi.Ch, Pi.Wa, Pi.Bu, Pi.Hi
order by cnt desc

который запускается мгновенно в студии управления SQL в нашей производственной базе данных. Я успешно написал код несколькими способами в С# LINQ и Entity Framework, но каждый раз код работает через 8-10 секунд. Одной из попыток является следующий код (без него, поскольку один вызов дает одинаковые результаты):

using(var context = new MyEntities()){
    var query = context.Products
        .Where(p => p.Code == codeFromFunctionArgument)
        .Join(context.Sis, p => p.Id, s => s.ProductId, (p, s) => new { sId = s.Id })
        .Join(context.Pis, ps => ps.sId, pi => pi.SiId, (ps, pi) => new {pi.Ch, pic.Wa, pic.Bu, pic.Hi})
        .GroupBy(
            pi => pi,
            (k, g) => new MostPisResult()
            {
                Ch = k.Ch,
                Wa = k.Wa,
                Bu = k.Bu,
                Hi = k.Hi,
                Count = g.Count()
            }
        )
        .OrderByDescending(x => x.Count);
        Console.WriteLine(query.ToString());
        return query.First();
    }
}

который выводит следующие операторы SQL:

SELECT 
    [Project1].[C2] AS [C1], 
    [Project1].[Ch] AS [Ch], 
    [Project1].[Wa] AS [Wa], 
    [Project1].[Bu] AS [Bu], 
    [Project1].[Hi] AS [Hi], 
    [Project1].[C1] AS [C2]
    FROM ( SELECT 
        [GroupBy1].[A1] AS [C1], 
        [GroupBy1].[K1] AS [Ch], 
        [GroupBy1].[K2] AS [Wa], 
        [GroupBy1].[K3] AS [Bu], 
        [GroupBy1].[K4] AS [Hi], 
        1 AS [C2]
        FROM ( SELECT 
            [Extent3].[Ch] AS [K1], 
            [Extent3].[Wa] AS [K2], 
            [Extent3].[Bu] AS [K3], 
            [Extent3].[Hi] AS [K4], 
            COUNT(1) AS [A1]
            FROM   [dbo].[Product] AS [Extent1]
            INNER JOIN [dbo].[Si] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ProductId]
            INNER JOIN [dbo].[Pi] AS [Extent3] ON [Extent2].[Id] = [Extent3].[SiId]
            WHERE ([Extent1].[Code] = @p__linq__0) AND (@p__linq__0 IS NOT NULL)
            GROUP BY [Extent3].[Ch], [Extent3].[Wa], [Extent3].[Bu], [Extent3].[Hi]
        )  AS [GroupBy1]
    )  AS [Project1]
    ORDER BY [Project1].[C1] DESC

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

Есть ли какая-то ошибка, которую я делаю при переводе запроса в LINQ? Есть ли очевидный способ, который я пропускаю, чтобы улучшить запрос? Можно ли написать запрос в EF/LINQ с той же производительностью, что и операторы SQL?

====== Обновление ======

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

exec sp_executesql N'SELECT TOP (1) 
    [Project1].[C2] AS [C1], 
    [Project1].[Ch] AS [Ch], 
    [Project1].[Wa] AS [Wa], 
    [Project1].[Bu] AS [Bu], 
    [Project1].[Hi] AS [Hi], 
    [Project1].[C1] AS [C2]
    FROM ( SELECT 
        [GroupBy1].[A1] AS [C1], 
        [GroupBy1].[K1] AS [Ch], 
        [GroupBy1].[K2] AS [Wa], 
        [GroupBy1].[K3] AS [Bu], 
        [GroupBy1].[K4] AS [Hi], 
        1 AS [C2]
        FROM ( SELECT 
            [Extent3].[Ch] AS [K1], 
            [Extent3].[Wa] AS [K2], 
            [Extent3].[Bu] AS [K3], 
            [Extent3].[Hi] AS [K4], 
            COUNT(1) AS [A1]
            FROM   [dbo].[Product] AS [Extent1]
            INNER JOIN [dbo].[Si] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ProductId]
            INNER JOIN [dbo].[Pi] AS [Extent3] ON [Extent2].[Id] = [Extent3].[SiId]
            WHERE ([Extent1].[Code] = @p__linq__0) AND (@p__linq__0 IS NOT NULL)
            GROUP BY [Extent3].[Ch], [Extent3].[Wa], [Extent3].[Bu], [Extent3].[Hi]
        )  AS [GroupBy1]
    )  AS [Project1]
    ORDER BY [Project1].[C1] DESC',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'109579'

====== Обновление 2 ======

Здесь запутанный XML-вывод плана выполнения запроса на Snipt.org. Обратите внимание, что данная переменная здесь называется "MagicalCode" на выходе, и оба значения "109579" и "2449-268-550" действительны (строки в С#), как в последней строке выходного файла XML.

<ParameterList>
    <ColumnReference
        Column="@p__linq__0"
        ParameterCompiledValue="N'109579'"
        ParameterRuntimeValue="N'2449-268-550'" />
</ParameterList>

Отобразить изображение с фактическими показателями строк

План

====== Обновление 3 ======

(скрыто в комментарии) Я запустил SQL файл, созданный EF, из сущности в SSMS, и он запускался мгновенно. Таким образом, я мог бы страдать от какой-то формы нюхания параметров, как это намекал этот вопрос. Я не уверен, как справиться с этим в контексте структуры сущностей.

====== Обновление 4 ======

Обновлен План выполнения SQL Entity Framework и Выполнение SQL запросов SQL SSMS Plan, который можно открыть с помощью Plan Explorer.

====== Обновление 5 ======

Некоторые попытки обхода пути

  • Запуск исходного запроса с помощью context.Database.SqlQuery<ReturnObject>(...) выполнялся через ~ 4-5 секунд.
  • Запуск исходного запроса с помощью SqlCommand и строка подключения, полученная из контекста EF, заняла около 3 секунд (служебные данные инициализации контекста).
  • Выполнение исходного запроса с использованием SqlCommand, взятого с жестко привязанной строкой, занимает около 1,5 секунд. Таким образом, я в конечном итоге использовал последний. Последнее, о чем я могу думать, это написать хранимую процедуру, чтобы приблизиться к "мгновенной" производительности запуска запроса в SSMS.

Ответ 1

Вы можете попробовать использовать IQueryable.AsNoTracking() см. http://msdn.microsoft.com/en-us/library/gg679352(v=vs.103).aspx. Безопасно использовать AsNoTracking() в тех случаях, когда вы не собираетесь редактировать результаты и снова сохранять их в базе данных. Обычно это имеет большое значение, когда Query возвращает большое количество строк. Убедитесь, что вы используете System.Data.Entity в своих целях, если вы хотите использовать .AsNoTracking()

Ответ 2

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

DBCC FREEPROCCACHE

Также этот поток может быть полезен: Производительность плана кеширования платформы Entity Framework ухудшается с различными параметрами