2015-01-27 7 views
15

У меня есть список с идентификаторами:Какую конструкцию я могу использовать вместо Содержит?

var myList = new List<int>(); 

Я хочу, чтобы выбрать все объекты из БД с идентификаторами из MyList:

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

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

The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information.

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

+0

вы можете попробовать избежать 'ToList' и конец и при необходимости, результаты? –

+0

Что вы используете? Linq to SQL? Какой вид db? Почему вы не рассматриваете использование хранимой процедуры? –

+1

@ Vignesh.N - Результат все равно потребует построения плана запроса, поэтому я не верю, что это изменило бы результат. –

ответ

4

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

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); 

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

+1

Просто некоторые другие соображения по этому подходу. Список ints может быть отсортирован перед разделением, а предложение where также может включать диапазон идентификаторов. Возможно, это обеспечило бы небольшую оптимизацию? –

+0

@TravisJ: хорошая идея действительно :) –

+0

Решение работает хорошо. Спасибо! –

5

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

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

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

+1

Звучит чрезмерно и жестко. Что делать, если идентификаторы были динамическими, а набор был разным каждый раз? –

+2

Да, это точка: OP должен создать эту временную таблицу для ** каждого ** запроса – DrKoch

+0

Это, похоже, не очень эффективное решение. –

14

Вы можете выполнить запрос на стороне клиента, добавив 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) 

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

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

+1

У меня такая же ошибка с 'HashSet'. –

+0

@KliverMax: Вы добавили 'AsEnumerable()'? –

+0

Извините, забыл о 'AsEnumerable()'. Whit это ваше решение работает. –

8

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

в вашей базе данных, используя 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 

Затем в C#

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]" 
     })); 
0

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

var objList= from obj in myContext.MyObjects 
    join myId in myList on obj.Id equals myId 
    select obj; 
Смежные вопросы