2015-01-30 2 views
0

у меня есть полный внешнее соединение запрос вытягивать данные из SQL Compact базы данных (я использую Ef6 для отображения):LINQ вложенных групп производительность

 var query = 
      from entry in left.Union(right).AsEnumerable() 
      select new 
      { 
       ... 
      } into e 
      group e by e.Date.Year into year 
      select new 
      { 
       Year = year.Key, 
       Quartals = from x in year 
          group x by (x.Date.Month - 1)/3 + 1 into quartal 
          select new 
          { 
           Quartal = quartal.Key, 
           Months = from x in quartal 
             group x by x.Date.Month into month 
             select new 
             { 
              Month = month.Key, 
              Contracts = from x in month 
                 group x by x.Contract.extNo into contract 
                 select new 
                 { 
                  ExtNo = month.Key, 
                  Entries = contract, 
                 } 
             } 
          } 
      }; 

, как вы можете видеть, я использовать вложенные группы, чтобы структурировать результаты. Интересно, если я удалю вызов AsEnumerable(), запрос займет 3,5 раза больше времени: ~ 210 мс против ~ 60 мс. И когда он запускается в первый раз, разница намного больше: 39000 (!) Мс против 1300 мс.

Мои вопросы:

  1. Что я делаю не так, может быть, эти группы должны делать по-другому?

  2. Почему первое исполнение занимает так много времени? Я знаю, что деревья выражений должны быть построены и т. Д., Но 39 секунд?

  3. Почему linq для db медленнее, чем linq для объектов в моем случае? Это вообще медленнее и лучше загружать данные из db, если это возможно, перед обработкой?

thakns!

+2

'AsEnumerable' переносит данные в память перед группировкой. Удаление это означает, что несколько подзапросов запускаются против db, что приводит к замедлению работы. – Enigmativity

ответ

2

Чтобы ответить на три вопроса:

Может быть эти группы должны делать по-другому?

Нет. Если вы хотите вложенные группировки, вы можете сделать это только с помощью группировок внутри группировок.

Вы можете группы по нескольким полям сразу:

from entry in left.Union(right) 
select new 
{ 
    ... 
} into e 
group e by new 
      { 
       e.Date.Year, 
       Quartal = (e.Date.Month - 1)/3 + 1, 
       e.Date.Month, 
       contract = e.Contract.extNo 
      } into grp 
select new 
{ 
    Year = grp.Key, 
    Quartal = grp.Key, 
    Month = grp.Key, 
    Contracts = from x in grp 
       select new 
       { 
        ExtNo = month.Key, 
        Entries = contract, 
       } 
} 

Это удалит много сложностей из сгенерированного запроса, так что, вероятно, будет (много) быстрее безAsEnumerable(). Но результат совсем другой: плоская группа (Год, Квартал и т. Д. В одной строке), а не вложенная группировка.

  1. Почему первое исполнение занимает так много времени?

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

3a. Почему linq db медленнее, чем linq для объектов в моем случае?

Поскольку, по-видимому, в этом случае гораздо эффективнее сначала извлекать данные в память и группировать объекты по объектам LINQ-to. Этот эффект будет более значительным, если left и right представляют собой более или менее сложные запросы. В этом случае сгенерированный SQL может сильно раздуваться, потому что он должен обрабатывать два источника сложности в одном выражении, что может привести к многократным идентичным подзапросам. Посредством аутсорсинга группировки база данных, вероятно, остается с относительным простым запросом, и, разумеется, сложность SQL-запроса не связана с группировкой в ​​памяти.

3b. Это вообще медленнее и лучше загружать данные из db, если это возможно, перед обработкой?

Нет, не в целом. Я бы даже сказал, почти никогда. В этом случае это потому, что (как я вижу) вы не фильтруете данные. Если, однако, часть до AsEnumerable() вернет миллионы записей, и после этого вы будете применять фильтрацию, запрос без AsEnumerable(), вероятно, будет намного быстрее, потому что фильтрация выполняется в базе данных.

Следовательно, вы всегда должны следить за сгенерированным SQL. Нереально ожидать, что EF всегда будет генерировать супер оптимизированную инструкцию SQL. Это вряд ли когда-либо будет. Его основное внимание уделяется правильности (и там выполняется исключительная работа), производительность является вторичной. Задача разработчика заключается в том, чтобы LINQ-to-Entities и LINQ-to-object работали вместе как гладкая команда.

+1

Кстати, в следующий раз было бы лучше задать один вопрос за раз. StackOverflow любит задавать вопросы, на которые можно ответить таким образом, что один ответ, скорее всего, пузырится как * * ответ. Это сложно, если три человека ответят на ваши три вопроса. –

1

Использование AsEnumerable() будет преобразовывать тип, который реализует IEnumerable<T> в IEnumerable<T>.

Читать эту тему https://msdn.microsoft.com/en-us/library/bb335435.aspx

AsEnumerable<TSource>(IEnumerable<TSource>) может быть использован для выбора реализаций запроса, когда последовательность реализует IEnumerable<T>, но и имеет другой набор методов общественного запроса доступны. Например, учитывая общий класс, который реализует TableIEnumerable<T> и имеет свои собственные методы, такие как Where, Select и SelectMany, вызов Where будет ссылаться общественным Where методом Table. A Table тип, представляющий таблицу базы данных, может иметь метод Where, который принимает предикатный аргумент как дерево выражений и преобразует дерево в SQL для удаленного выполнения. Если удаленное выполнение нежелательно, например, поскольку предикат вызывает локальный метод, метод AsEnumerable<TSource> может использоваться для скрытия настраиваемых методов и вместо этого сделать доступными стандартные операторы запроса.

Когда вы вызываете AsEnumerable() во-первых, он не будет преобразовывать LINQ-to-SQL, а вместо этого загружает таблицу в память, поскольку перечисляет Where. Поскольку теперь он загружен в память, его выполнение выполняется быстрее.

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