2014-11-14 3 views
3

У меня есть запрос LINQ, который получает данные через Entity Framework Code Сначала из базы данных SQL. Это работает, но работает очень медленно.Оптимизация запросов LINQ для медленной группировки

Это оригинальный запрос:

 var tmpResult = from mdv in allMetaDataValues 
        where mdv.Metadata.InputType == MetadataInputType.String && mdv.Metadata.ShowInFilter && !mdv.Metadata.IsHidden && !string.IsNullOrEmpty(mdv.ValueString) 
        group mdv by new 
        { 
        mdv.ValueString, 
        mdv.Metadata 
        } into g 
        let first = g.FirstOrDefault() 
        select new 
        { 
        MetadataTitle = g.Key.Metadata.Title, 
        MetadataID = g.Key.Metadata.ID, 
        CollectionColor = g.Key.Metadata.Collection.Color, 
        CollectionID = g.Key.Metadata.Collection.ID, 

        MetadataValueCount = 0, 
        MetadataValueTitle = g.Key.ValueString, 
        MetadataValueID = first.ID 
        }; 

Это сгенерированный SQL из исходного запроса:

{SELECT 
0 AS [C1], 
[Project4].[Title] AS [Title], 
[Project4].[ID] AS [ID], 
[Extent9].[Color] AS [Color], 
[Project4].[Collection_ID] AS [Collection_ID], 
[Project4].[ValueString] AS [ValueString], 
[Project4].[C1] AS [C2] 
FROM (SELECT 
    [Project2].[ValueString] AS [ValueString], 
    [Project2].[ID] AS [ID], 
    [Project2].[Title] AS [Title], 
    [Project2].[Collection_ID] AS [Collection_ID], 
    (SELECT TOP (1) 
     [Filter4].[ID1] AS [ID] 
     FROM (SELECT [Extent6].[ID] AS [ID1], [Extent6].[ValueString] AS [ValueString], [Extent7].[Collection_ID] AS [Collection_ID1], [Extent8].[ID] AS [ID2], [Extent8].[InputType] AS [InputType], [Extent8].[ShowInFilter] AS [ShowInFilter], [Extent8].[IsHidden] AS [IsHidden1] 
      FROM [dbo].[MetadataValue] AS [Extent6] 
      LEFT OUTER JOIN [dbo].[Media] AS [Extent7] ON [Extent6].[Media_ID] = [Extent7].[ID] 
      INNER JOIN [dbo].[Metadata] AS [Extent8] ON [Extent6].[Metadata_ID] = [Extent8].[ID] 
      WHERE (NOT (([Extent6].[ValueString] IS NULL) OR ((CAST(LEN([Extent6].[ValueString]) AS int)) = 0))) AND ([Extent7].[IsHidden] <> cast(1 as bit)) 
     ) AS [Filter4] 
     WHERE (2 = CAST([Filter4].[InputType] AS int)) AND ([Filter4].[ShowInFilter] = 1) AND ([Filter4].[IsHidden1] <> cast(1 as bit)) AND ([Filter4].[Collection_ID1] = @p__linq__0) AND (([Project2].[ValueString] = [Filter4].[ValueString]) OR (([Project2].[ValueString] IS NULL) AND ([Filter4].[ValueString] IS NULL))) AND (([Project2].[ID] = [Filter4].[ID2]) OR (1 = 0))) AS [C1] 
    FROM (SELECT 
     [Distinct1].[ValueString] AS [ValueString], 
     [Distinct1].[ID] AS [ID], 
     [Distinct1].[Title] AS [Title], 
     [Distinct1].[Collection_ID] AS [Collection_ID] 
     FROM (SELECT DISTINCT 
      [Filter2].[ValueString] AS [ValueString], 
      [Filter2].[ID3] AS [ID], 
      [Filter2].[InputType1] AS [InputType], 
      [Filter2].[Title1] AS [Title], 
      [Filter2].[ShowInFilter1] AS [ShowInFilter], 
      [Filter2].[IsHidden2] AS [IsHidden], 
      [Filter2].[Collection_ID2] AS [Collection_ID] 
      FROM (SELECT [Filter1].[ValueString], [Filter1].[Collection_ID3], [Filter1].[IsHidden3], [Filter1].[ID3], [Filter1].[InputType1], [Filter1].[Title1], [Filter1].[ShowInFilter1], [Filter1].[IsHidden2], [Filter1].[Collection_ID2] 
       FROM (SELECT [Extent1].[ValueString] AS [ValueString], [Extent2].[Collection_ID] AS [Collection_ID3], [Extent4].[IsHidden] AS [IsHidden3], [Extent5].[ID] AS [ID3], [Extent5].[InputType] AS [InputType1], [Extent5].[Title] AS [Title1], [Extent5].[ShowInFilter] AS [ShowInFilter1], [Extent5].[IsHidden] AS [IsHidden2], [Extent5].[Collection_ID] AS [Collection_ID2] 
        FROM  [dbo].[MetadataValue] AS [Extent1] 
        LEFT OUTER JOIN [dbo].[Media] AS [Extent2] ON [Extent1].[Media_ID] = [Extent2].[ID] 
        INNER JOIN [dbo].[Metadata] AS [Extent3] ON [Extent1].[Metadata_ID] = [Extent3].[ID] 
        LEFT OUTER JOIN [dbo].[Metadata] AS [Extent4] ON [Extent1].[Metadata_ID] = [Extent4].[ID] 
        LEFT OUTER JOIN [dbo].[Metadata] AS [Extent5] ON [Extent1].[Metadata_ID] = [Extent5].[ID] 
        WHERE (NOT (([Extent1].[ValueString] IS NULL) OR ((CAST(LEN([Extent1].[ValueString]) AS int)) = 0))) AND ([Extent2].[IsHidden] <> cast(1 as bit)) AND (2 = CAST([Extent3].[InputType] AS int)) AND ([Extent3].[ShowInFilter] = 1) 
       ) AS [Filter1] 
       WHERE [Filter1].[IsHidden3] <> cast(1 as bit) 
      ) AS [Filter2] 
      WHERE [Filter2].[Collection_ID3] = @p__linq__0 
     ) AS [Distinct1] 
    ) AS [Project2]) AS [Project4] 
LEFT OUTER JOIN [dbo].[Collection] AS [Extent9] ON [Project4].[Collection_ID] = [Extent9].[ID]} 

Если убрать "пусть первый = g.FirstOrDefault()" и изменить «MetadataValueID = first.ID« to »MetadataValueID = 0« так, чтобы у нас только фиксированный ID = 0 для целей тестирования, тогда d ата нагрузки очень быстро, и сам сгенерированный запрос в два раза меньше по сравнению с первоначальным Таким образом, кажется, что эта часть делает запрос очень медленно:

let first = g.FirstOrDefault() 
... 
    MetadataValueID = first.ID 
}; 

Как это может быть переписано? Если я пытаюсь переписать код, он по-прежнему медленно:

MetadataValueID = g.Select(x => x.ID).FirstOrDefault() 

или

let first = g.Select(x => x.ID).FirstOrDefault() 
... 
    MetadataValueID = first 
}; 

Любые предложения?

+0

Трудно знать, не видя схему или сгенерированный SQL, но я думаю, что 'FirstOrDefault()' рискованно - если не есть, то 'first.ID' будет ошибкой. Возможно, EF сбивает с толку. Я бы попытался изменить его на 'g.First()' и перенести его в результат - '... в g select new {..., MetadataValueID = g.First(). ID}' – Rhumborl

+0

@Rhumborl i have добавлен исходный код sql – juFo

+0

Что означает 'allMetadataValues'? Я ожидал бы что-то вроде 'dbContext.MetadataValues'. –

ответ

1

Использование EF Я всегда чувствовал, что у него есть проблемы с эффективностью перевода таких вещей, как g.Key.Metadata.Collection, поэтому я пытаюсь присоединиться к более явным образом и включать только те поля, которые необходимы для вашего результата. Вы можете использовать include вместо соединения с использованием шаблона репозитория.

Тогда ваш запрос будет выглядеть следующим образом:

from mdv in allMetaDataValues.Include("Metadata").Include("Metadata.Collection") 
    where mdv.Metadata.InputType == MetadataInputType.String && 
     mdv.Metadata.ShowInFilter && 
     !mdv.Metadata.IsHidden && 
     !string.IsNullOrEmpty(mdv.ValueString) 
    group mdv by new 
    { 
    MetadataID = mdv.Metadata.ID, 
    CollectionID = mdv.Metadata.Collection.ID, 
    mdv.Metadata.Title, 
    mdv.Metadata.Collection.Color, 
    mdv.ValueString 
    } into g 
    let first = g.FirstOrDefault().ID 
    select new 
    { 
    MetadataTitle = g.Key.Title, 
    MetadataID = g.Key.MetadataID, 
    CollectionColor = g.Key.Color, 
    CollectionID = g.Key.CollectionID, 
    MetadataValueCount = 0, 
    MetadataValueTitle = g.Key.ValueString, 
    MetadataValueID = first 
    } 

Хороший инструмент для воспроизведения с помощью LINQ является LinqPad.

Проблема заключается в том, что также:

let first = g.FirstOrDefault().ID 

не могут быть легко переведены на SQL см this answer. Но этот переписать упрощает базовый запрос для него, по крайней мере. Мне остается неясным, почему вам нужен первый идентификатор из набора без использования orderby.

Это может быть переписан так:

let first = (from f in allMetaDataValues 
       where f.Metadata.ID == g.Key.MetadataID && 
        f.ValuesString == g.Key.ValuesString select f.ID) 
      .FirstOrDefault() 

Таким образом, вы не позволяете EF написать запрос для вас, и вы можете точно определить, как сделать выбор. Чтобы ускорить запрос, вы также можете рассмотреть возможность добавления индексов в базу данных в соответствии с сгенерированным запросом, а именно с индексом, использующим оба столбца, используемые в предложении where этого запроса let first.

+0

Я использую репозиторий, поэтому я не могу получить доступ к «db». чтобы присоединиться к себе. – juFo

+1

@juFo Отредактирован ответ на работу с шаблоном репозитория, добавлен переписать для предложения 'let first'. –

+0

, что g.FirstOrDefault(). ID rewrite, похоже, делает окончательный трюк – juFo

0

Попробуйте следующее решение.
Заменить FirstOrDefault() на .Take(1). FirstOrDefault() не лениво загружен.

var tmpResult = from mdv in allMetaDataValues 
        where mdv.Metadata.InputType == MetadataInputType.String && mdv.Metadata.ShowInFilter && !mdv.Metadata.IsHidden && !string.IsNullOrEmpty(mdv.ValueString) 
        group mdv by new 
        { 
        mdv.ValueString, 
        mdv.Metadata 
        } into g 
        let first = g.Take(1) 
        select new 
        { 
        MetadataTitle = g.Key.Metadata.Title, 
        MetadataID = g.Key.Metadata.ID, 
        CollectionColor = g.Key.Metadata.Collection.Color, 
        CollectionID = g.Key.Metadata.Collection.ID, 

        MetadataValueCount = 0, 
        MetadataValueTitle = g.Key.ValueString, 
        MetadataValueID = first.ID 
        }; 
+0

first.ID невозможно, потому что Take (1) возвращает IEnumerable juFo

+0

Кроме того 'let first = g.FirstOrDefault()' преобразуется как 'SELECT TOP (1) 'уже. –

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