Исключение большого List‹int› из запроса LINQ to Entities

У меня есть список, содержащий большое количество элементов - до 10 000.

Я ищу наиболее эффективный способ исключить их из списка IQueryable/List.

Из-за сложности процесса, связанного с получением этого списка идентификаторов, это невозможно сделать в запросе.

Пример ниже имеет чрезвычайно высокие накладные расходы, и интересно, может ли кто-нибудь объяснить возможные причины этого и есть ли лучший способ добиться этого?

results = from q1 in results 
  where excludedRecords.All(x => x != q1.ItemId)
  select q1;

person Nick    schedule 13.12.2013    source источник
comment
Сколько элементов в результатах? Сколько в excludeRecords. Какой тип исключенных записей?   -  person rene    schedule 13.12.2013
comment
LINQ в конечном итоге сводится к циклу for (в моем понимании). Я предполагаю, что могут быть способы оптимизировать это, ограничив количество элементов, которые вы перебираете. Кстати, вы говорите, что это невозможно сделать в запросе. Возможно, не в одном запросе, но у меня такое чувство, что должен быть способ использовать SQL, чтобы помочь вам. Имея это в виду, может быть, стоит найти и протестировать подход к запросу?   -  person drew_w    schedule 13.12.2013
comment
@drew_w этот комментарий просто неверен. Linq не сводится к циклу for   -  person Hogan    schedule 13.12.2013
comment
@Hogan Согласно этому стеку лямбда LINQ компилируется в функцию , затем это расширяется до соответствующего синтаксиса цикла (IEnumerable в конечном итоге также разрешается в цикл). Является ли мое понимание этого неправильным?   -  person drew_w    schedule 13.12.2013
comment
Насколько я понимаю, причина, по которой люди любят LINQ, заключается в удобочитаемости, а также в том, что скомпилированная лямбда не вызывается до тех пор, пока не будет получен доступ к переменной.   -  person drew_w    schedule 13.12.2013
comment
@drew_w - это неверно из-за введенной оптимизации (хеширование операндов делает это намного быстрее, чем стандартные операнды) и ленивой оценки. Трудно назвать ленивое вычисление циклом в традиционном смысле.   -  person Hogan    schedule 13.12.2013
comment
@Hogan Технически вы говорите, что это сводится к циклу в умеренно хорошо написанной функции. Не поймите меня неправильно — мне очень нравится LINQ, я просто думаю, что стоит помнить, что рано или поздно он все равно должен зацикливаться, поэтому ограничение количества результатов, которые он должен обрабатывать, безусловно, повлияет на производительность. Именно поэтому я говорил о том, что это петля. Надеюсь, это имеет смысл!   -  person drew_w    schedule 13.12.2013
comment
Это именно тот тип оптимизации, который будет работать в этом случае — пользователь хочет, чтобы хэш искал существование — если он правильно спросит linq, он, вероятно, увидит увеличение скорости примерно на 3 порядка по сравнению с традиционной таблицей поиска с петля.   -  person Hogan    schedule 13.12.2013
comment
@drew_w - Нет, технически это не сводится к циклу, это данность, мы говорим технически, это StackOverflow.com. Вы бы назвали JOIN, выполняемый на SQL Server, циклом? Нет, вы должны посмотреть на план выполнения и увидеть, что он сделал. Linq использует ту же технологию, когда это возможно, он выполняет хэшированный поиск и получает производительность O (1) в качестве op. к производительности O (N) цикла, вы НЕ увидите большого изменения производительности в зависимости от количества элементов.   -  person Hogan    schedule 13.12.2013
comment
@Hogan Вы упомянули хеширование, но создание хеш-карты / таблицы данных также требует времени и цикла. Я не понимаю, как LINQ может извлекать данные из базы данных и никогда не повторять их хотя бы один раз. Можете ли вы предоставить мне некоторые документы, подтверждающие это? Я далек от эксперта в LINQ, поэтому я просто честно пытаюсь понять, как это работает. Я был и буду продолжать искать в Google, чтобы увидеть, что я могу найти!   -  person drew_w    schedule 13.12.2013
comment
На самом деле, может быть, я перенесу это в вопрос, а не просто поговорю в комментариях! :)   -  person drew_w    schedule 13.12.2013
comment
@drew_w - моя электронная почта находится в моем профиле, напишите мне, и я пришлю вам несколько примеров, с которыми вы можете поиграть.   -  person Hogan    schedule 13.12.2013
comment
@Hogan Я не смог найти многого в Google, но я хочу поделиться с другими в сообществе, поэтому я пошел дальше и спросил новый вопрос. Если вы все еще заинтересованы в обмене, давайте переместим разговор туда! :)   -  person drew_w    schedule 13.12.2013


Ответы (2)


Это всего лишь фрагмент кода, но похоже, что у вас есть два списка — results и excludeRecords. Для каждого элемента в результатах вы выполняете итерацию по всем элементам в excludeRecords. Вот почему он медленный, он O(N x M)

Linq и sql решают это с помощью присоединения, если вы присоединитесь (или эквивалентно), вы должны увидеть хорошую производительность, так как это будет что-то вроде O(NlgM)

Это будет выглядеть примерно так (сейчас не могу проверить)

var results2 = from q1 in results
                join x in excludedRecords on q1.LeadID = x into joined
                from z in joined.DefaultIfEmpty()
                where z == null
                select q1;
person Hogan    schedule 13.12.2013
comment
Спасибо за комментарии Хоган. Запуск вышеизложенного против результатов приводит к переполнению стека. Объект результатов имеет тип IQueryable‹MyObject›. - person Nick; 13.12.2013
comment
@Nick - мне нужно проверить себя, чтобы ответить - у меня, вероятно, опечатка, но вы поняли, сделайте левое соединение и возьмите только те, которые равны нулю, как в SQL. Я уверен, что есть несколько примеров, если вы погуглите. Нет возможности проверить сейчас, к сожалению. - person Hogan; 13.12.2013
comment
Проблема в том, что вы не можете присоединиться к списку в памяти с IQueryable из Entity Framework. - person Gert Arnold; 13.12.2013

Судя по форме вашего запроса, я считаю excludedRecords списком целых чисел. Кроме того, поскольку вы помечаете LINQ to Entities, я принимаю results за DbSet в DbContext.

Это проблема объединения локальных списков (excludedRecords) с IQueryable, ожидающим перевода в SQL (results). Чтобы EF мог перевести полное выражение (ваш запрос) в SQL, он должен преобразовать этот локальный список в «что-то», что может быть частью оператора SQL. С помощью All() и многих других операторов LINQ на основе наборов, а также при присоединении к локальному списку EF делает это, создавая временную таблицу (своего рода) из однострочных таблиц. Если в локальном списке всего 5 элементов, это выглядит так

SELECT ...
    FROM [dbo].[Table] AS [Extent1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            1 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            2 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            3 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]
        UNION ALL
            SELECT 
            4 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable4]
        UNION ALL
            SELECT 
            5 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4]
        WHERE ([Extent1].[Id] = [UnionAll4].[C1]) OR (CASE WHEN ([Extent1].[Id] <> [UnionAll4].[C1]) THEN cast(1 as bit) WHEN ([Extent1].[Id] = [UnionAll4].[C1]) THEN cast(0 as bit) END IS NULL)
    )

Несмотря на то, что потенциально это может привести к огромным SQL-запросам, это все же работает, когда локальный список не содержит «слишком много» элементов (скажем, до 1000).

Единственный оператор, который позволяет EF более эффективно использовать локальный список, — это Contains. Contains можно легко преобразовать в оператор SQL IN. Если мы перепишем ваш запрос на эквивалент с Contains, что также является ответом на ваш вопрос, ...

results = from q1 in results 
          where !excludedRecords.Contains(q1.ItemId)
          select q1;

... SQL-запрос будет выглядеть так

SELECT ...
    FROM [dbo].[Table] AS [Extent1]
    WHERE  NOT ([Extent1].[Id] IN (1, 2, 3, 4, 5))

Оператор IN может обрабатывать больше элементов, чем эта «временная таблица», хотя это число все еще ограничено (возможно, 3000).

person Gert Arnold    schedule 14.12.2013