Какую конструкцию я могу использовать вместо Contains?

У меня есть список с идентификаторами:

var myList = new List<int>();

Я хочу выбрать все объекты из db с идентификаторами из myList:

var objList= myContext.MyObjects.Where(t => myList.Contains(t.Id)).ToList();

Но когда myList.Count > 8000 я получаю сообщение об ошибке:

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

Я думаю, что это потому, что я использовал Contains(). Что я могу использовать вместо Contains?

Ответы

Ответ 1

Вы можете разбить список в нескольких под-списках и запустить отдельные запросы:

int start = 0;
int count = 0;
const int chunk_size = 1000;
do {
    count = Math.Min(chunk_size, myList.Count - start);
    var tmpList = myList.GetRange(start, count);
    // run query with tmpList
    var objList= myContext.MyObjects.Where(t => tmpList.Contains(t.Id)).ToList();
    // do something with results...
    start += count;
} while (start < myList.Count);

Конечно, вам нужно найти хороший "размер куска" каким-то образом, который сработает для вас. В зависимости от размера таблицы и списка, было бы удобнее загружать всю таблицу и фильтровать в код, как это предложено в других ответах.

Ответ 2

Вы можете выполнить запрос на стороне клиента, добавив AsEnumerable(), чтобы "скрыть" предложение Where от Entity Framework:

var objList = myContext
  .MyObjects
  .AsEnumerable()
  .Where(t => myList.Contains(t.Id))
  .ToList();

Чтобы повысить производительность, вы можете заменить список HashSet:

var myHashSet = new HashSet<int>(myList);

а затем изменить предикат в Where соответственно:

  .Where(t => myHashSet.Contains(t.Id))

Это "простое" решение с точки зрения времени реализации. Однако, поскольку запрос работает на стороне клиента, вы можете получить низкую производительность, потому что все строки MyObjects вытягиваются на клиентскую сторону, прежде чем они будут отфильтрованы.

Причина, по которой вы получаете ошибку, заключается в том, что Entity Framework преобразует ваш запрос в нечто вроде этого:

SELECT ...
FROM ...
WHERE column IN (ID1, ID2, ... , ID8000)

Таким образом, все 8000 ID из списка включены в сгенерированный SQL, который превышает предел того, что может обрабатывать SQL Server.

Что Entity Framework "ищет" для создания этого SQL - это ICollection<T>, который реализуется как List<T>, так и HashSet<T>, поэтому, если вы попытаетесь сохранить запрос на стороне сервера, вы не получите улучшенной производительности, используя HashSet<T>. Однако на стороне клиента история отличается от Contains O(1) для HashSet<T> и O(N) для List<T>.

Ответ 3

Если вы этого не сделаете, я предлагаю вам использовать параметры таблицы и хранимую процедуру.

в вашей базе данных, используя TSQL,

CREATE TYPE [dbo].[IdSet] AS TABLE
(
    [Id] INT
);
GO

CREATE PROCEDURE [dbo].[Get<table>]
    @ids [dbo].[IdSet] READONLY
AS
    SET NOCOUNT ON;

    SELECT
                <Column List>
        FROM
                [dbo].[<table>] [T]
        WHERE
                [T].[Id] IN (SELECT [Id] FROM @ids);
RETURN 0;
GO

Тогда в С#

var ids = new DataTable()
ids.Columns.Add("Id", typeof(int));

foreach (var id in myList)
{
    ids.Rows.Add(id);
}

var objList = myContext.SqlQuery<<entity>>(
    "[dbo].[Get<table>] @ids",
    new SqlParameter("@ids", SqDbType.Structured)
        { 
            Value = ids,
            TypeName = "[dbo].[IdSet]"
        }));

Ответ 4

Вы можете создать временную таблицу базы данных, которая представляет myList, и реорганизовать ваш запрос на JOIN с этим временным списком.

Причиной ошибки является то, что полученный фактический запрос содержит все элементы myList.

В основном БД (процессор запросов) должен видеть оба списка для фильтрации. Если второй список слишком велик, чтобы помещаться внутри запроса, вы должны предоставить его в противном случае (например, как временную таблицу).

Ответ 5

Почему бы не попробовать

var objList= from obj in myContext.MyObjects
     join myId in myList on obj.Id equals myId
     select obj;