2013-01-17 2 views
5

Мне было предложено подготовить отчет, в котором содержится довольно сложный SQL-запрос к базе данных SQL Server. Поскольку сайт отчета уже использовал Entity Framework 4.1, я думал, что я попытался бы написать запрос с помощью EF и LINQ:Есть ли способ оптимизировать этот запрос LINQ to Entities?

var q = from r in ctx.Responses 
        .Where(x => ctx.Responses.Where(u => u.UserId == x.UserId).Count() >= VALID_RESPONSES) 
        .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText }) 
     orderby r.FirstOrDefault().User.AwardCity, r.FirstOrDefault().Category.Label, r.Count() descending 
     select new 
     { 
      City = r.FirstOrDefault().User.AwardCity, 
      Category = r.FirstOrDefault().Category.Label, 
      Response = r.FirstOrDefault().ResponseText, 
      Votes = r.Count() 
     }; 

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

Этот подход был полной катастрофой с точки зрения производительности, поэтому мы переключились на ADO.NET, и запрос запустился очень быстро. Я просмотрел LINQ сгенерированный SQL, используя SQL Profiler, и хотя он выглядел ужасно, как обычно, я не видел никаких подсказок относительно того, как оптимизировать оператор LINQ, чтобы сделать его более эффективным.

Вот прямая версия TSQL:

WITH ValidUsers(UserId) 
AS 
(
    SELECT UserId 
    FROM Responses 
    GROUP BY UserId 
    HAVING COUNT(*) >= 103 
) 
SELECT d.AwardCity 
    , c.Label 
    , r.ResponseText 
    , COUNT(*) AS Votes 
FROM ValidUsers u 
JOIN Responses r ON r.UserId = u.UserId 
JOIN Categories c ON r.CategoryId = c.CategoryId 
JOIN Demographics d ON r.UserId = d.Id 
GROUP BY d.AwardCity, c.Label, r.ResponseText 
ORDER BY d.AwardCity, s.SectionName, COUNT(*) DESC 

Что мне интересно: является ли это запрос слишком сложный для EF и LINQ для обработки эффективно или я пропустил трюк?

+0

Я предполагаю, что все FirstOrDefaults вызывают это. Вы пытались добавить 'let response = r.First()' перед группой? Или поменять Select и OrderBy? Как это http://stackoverflow.com/a/5013740/736079 – jessehouwing

+2

Есть ли свойство навигации, например 'User.Responses'? –

+0

@jessehouwing с использованием let response помогает значительно, хотя версия LINQ все еще намного медленнее, чем ADO.NET. Если вы введете это в качестве ответа, я, по крайней мере, выберу его. У меня возникли проблемы с стратегией поменять и упорядочивать Jon Skeet, в основном я не могу понять, как получить счет с помощью этой конструкции. –

ответ

4

Использование подведенного, чтобы уменьшить количество r.First(), вероятно, улучшит производительность. Вероятно, этого пока недостаточно.

var q = from r in ctx.Responses 
       .Where() 
       .GroupBy() 
    let response = r.First() 
    orderby response.User.AwardCity, response.Category.Label, r.Count() descending 
    select new 
    { 
     City = response.User.AwardCity, 
     Category = response.Category.Label, 
     Response = response.ResponseText, 
     Votes = r.Count() 
    }; 
+0

Я отмечаю это как ответ, потому что его эффект примерно эквивалентен решению навигационной функции, предложенному @GertArnold, но Герт еще не отправил свой комментарий в качестве ответа (извините, Герт, я вас позову). Следует отметить, что даже с оптимизацией ADO.NET все еще быстрее, но пересмотренный LINQ на порядок быстрее, чем был. –

+1

Вы также можете проголосовать за комментарии. – jessehouwing

1

Может быть, это изменение улучшить производительность, удаление в результате вложенный SQL выберите в котором пункт

Сначала получают голоса каждого пользователя и поместить их в Dictionary

var userVotes = ctx.Responses.GroupBy(x => x.UserId) 
          .ToDictionary(a => a.Key.UserId, b => b.Count()); 

var cityQuery = ctx.Responses.ToList().Where(x => userVotes[x.UserId] >= VALID_RESPONSES) 
       .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText }) 
       .Select(r => new 
         { 
          City = r.First().User.AwardCity, 
          Category = r.First().Category.Label, 
          Response = r.First().ResponseText, 
          Votes = r.Count() 
         }) 
       .OrderByDescending(r => r.City, r.Category, r.Votes()); 
+0

Я уже пробовал этот подход. Вот ошибка: LINQ to Entities не распознает метод «Int32 get_Item (Int32)», и этот метод не может быть переведен в выражение хранилища –

+0

Я думаю, что я пропустил, чтобы добавить ToList() после ctx.Responses, в результате чего ctx.Responses.ToList(). Где (.... –

+0

Я думаю, что основная проблема заключается в том, что драйвер EF LINQ не может использовать ссылку на внешний словарь, когда он создает объект SQL. Я уверен, что скопировал и вставил ваше решение буквально, все, что я сделал, - это исправление синтаксиса первого ToDictionary lambda. –

Смежные вопросы