2015-10-15 2 views
12

Entity Framework 5+ должен предварительно скомпилировать все запросы. Однако, для запросов, таких какEntity Framework: предварительно скомпилированный запрос для перечисляемых.Contains

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 

Entity Framework не может PreCompile запроса, и в зависимости от сложности общего запроса, парсинг дерева выражений в SQL может потреблять несколько секунд. Кто-нибудь нашел обходное решение, чтобы получить предварительно скомпилированный запрос? Я не совсем понимаю, почему так сложно; конечно, это трудно сделать с paramters, так как число элементов может отличаться, но это было бы достаточно, чтобы иметь SQL как

SELECT a, b, c from MyEntities 
WHERE c in __PLACEHOLDER__ 

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

+0

Списки не могут использоваться в качестве параметров, поэтому он предлагает то, что вы предлагаете, что он создает новый запрос, потому что ваш список может содержать разные элементы при каждом его вызове. Таким образом, это фактически ограничение SQL. –

+0

Не совсем; как сказано, он начинает анализировать дерево выражений с нуля каждый раз.у нас есть запрос с паролем соединений, который занимает 5 секунд для разбора (с несколькими миллисекундами на сервере sql), поэтому я ищу обходной путь. – Roland

+1

@rolandHow большой этот список? Также есть ли у вас аксессуар для использования? Проблема может быть в зависимости от того, как долго каждая запись будет ... насколько он должен сравниться. У меня была аналогичная проблема с .StartsWith до тех пор, пока я не использовал StringComparison.Ordinal, который значительно ускорил его (по сравнению с .Contains). Проблема может заключаться в том, что ему нужно перебирать слишком большие строки (к большому с точки зрения времени требуется время). Если вы можете изменить его для начала и порядкового номера, он должен значительно расшириться (но зависит от вашего конкретного варианта использования). – Thomas

ответ

6

Вы должны сначала понять, как работает оператор «IN» в параметризованном SQL-запросе.

не работает, параметр команды SQL не принимает ARRAY в качестве значения параметра, а запрос переводится на

SELECT A FROM B WHERE C IN (@p1, @p2, @p3 ... etc) 

Этот запрос переменное число параметров, и это причина, есть нет возможности прекомпилировать этот запрос с помощью IEnumerable.Contains.

Единственная альтернатива (длинный длинный путь) - использовать Xml или Json (входит в Sql 2016).

Сохраните IEnumerable как xml.

[10,20,20,50] can be translated to 
<data> 
    <int value="10"/> 
    <int value="20"/> 
    <int value="20"/> 
    <int value="50"/> 
</data> 

И тогда вы можете определить VIEW с параметрами,

SELECT, А ОТ B WHERE C IN (SELECT INT FROM Xml (@ P1))

И вы можете использовать этот вид, однако в EF есть больше проблем с тем, как запустить этот запрос, но этот запрос можно предварительно скомпилировать, поскольку он имеет только один параметр.

Пользовательского SQL для производительности Hack

Для довольно простого запроса, как,

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 

я мог бы просто использовать пользовательский SQL и огнь,

var parameterList = ids.Select( 
    (x,i)=> new SqlCommandParameter(
     "@p"+i, x)); 

var pnames = String.Join(",", parameterList.Select(x=> x.ParameterName)); 

var entities = 
    context.SqlQuery<MyEntity>(
     "SELECT * FROM TABLE WHERE Id in (" + pnames + ")", 
     parameterList.ToArray()); 

временной таблицу

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

Guid sid = Guid.NewGuid(); 
foreach(var p in ids){ 
    db.TempIDs.Add(new TempID{ SID = sid, Value = p }); 
} 
db.SaveChanges(); 

var qIDs = db.TempIDs.Where(x=> x.SID == sid); 

var myEntities db.MyEntities.Where(x => qIDs.Any(q.Value == x.Id)); 

// delete all TempIDs... 
db.SqlQuery("DELETE FROM TempIDs WHERE [email protected], 
    new SqlCommandParameter("@sid", sid)); 
+0

Спасибо за этот ответ; к сожалению, я все еще не то, что ищу. Как упоминалось выше, у нас есть много таких запросов, и писать много пользовательских SQL/Views на самом деле не так, как тогда, мы закроем ремонтопригодность приложения. Интересно, есть ли способ добавить временный тип в EF. Идея: мы могли бы добавить временную таблицу для хранения списка параметров Содержит, а затем ввести временный тип для формулировки запроса. Кто-нибудь знает, разрешает ли EF вводить временные типы? – Roland

+0

Да, вместо XML или JSON вы также можете использовать временную таблицу. но это добавит накладные расходы для базы данных, это увеличит количество активных транзакций, что приведет к низкой производительности благодаря движку базы данных. –

+0

Да, но могу ли я добавить временный тип C# в EF? Мне понадобится это для формулировки запроса, например context.MyEntities.Where (x => context.Set (typeof (TemporaryType)). Содержит (x.Id)). Я бы не прочь сделать все необходимое для размышлений. – Roland