Привязка к скомпилированному запросу теряет преимущество в производительности

#linq #entity-framework #linq-to-entities

#linq #entity-framework #linq-to-entities

Вопрос:

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

Кажется, в этом конкретном случае я теряю преимущество в производительности скомпилированных запросов. Кто-нибудь может объяснить, почему?

Вот пример того, что я делаю…

 IEnumerable<Task> tasks = compiledQuery.Invoke(context, userId); 

if(status != null)
{
    tasks = tasks.Where(x=x.Status == status);
}
if(category != null)
{
   tasks = tasks.Where(x=x.Category == category);
}

return tasks;
  

Ответ №1:

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

При выполнении запроса Entity Framework сопоставит ваше дерево выражений с помощью вашего файла сопоставления (EDMX или с помощью code first ваших определений модели) с SQL-запросом. Это может быть сложной задачей, требующей больших затрат производительности.

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

Проблема в том, что предварительно скомпилированный запрос потеряет преимущество в производительности, как только вы измените запрос. Допустим, у вас есть следующее:

 IQueryable query = GetCompiledQuery(); // => db.Tasks.Where(t => t.Id == myId);
var notModifiedResult = query.ToList(); // Fast
int ModifiedResult = query.Count(); // Slow
  

С первым запросом у вас будут все преимущества предварительной компиляции, потому что EF уже сгенерировал для вас SQL и может выполнить это немедленно.
Второй запрос потеряет предварительную компиляцию, потому что он должен повторно использовать свой SQL.

Если бы вы сейчас выполнили запрос на notModifiedResult , это была бы ссылка на Objects one, потому что вы уже выполнили свой SQL для базы данных и извлекли все элементы в памяти.

Однако вы можете связать скомпилированные запросы по цепочке (то есть использовать скомпилированный запрос в другом скомпилированном запросе).

Но для вашего кода потребуется серия скомпилированных запросов: — По умолчанию — Один, где status != null — Один, где category != null — Один, где и status, и category != null

Ответ №2:

(Примечание: я целую вечность не выполнял никакой работы с EF, а потом это было просто возней. На самом деле это всего лишь обоснованное предположение.)

Это может быть причиной:

 IEnumerable<Task> tasks = compiledQuery.Invoke(context, userId);
  

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

 IQueryable<Task> tasks = compiledQuery.Invoke(context, userId);
  

(При условии, что это допустимо, конечно.)

Ответ №3:

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

 .Invoke(context, userId);  // returns all the results
.Where(....) // filters on that entire collection
  

Вы можете посмотреть, есть ли умный способ повторно сформулировать ваш запрос, чтобы параметры могли быть включены во всех случаях, но не иметь никакого эффекта. Я не работал со скомпилированными запросами, извините за это, но работает ли это (используя -1 в качестве значения «игнорировать»)?

 // bunch of code to define the compiled query part, copied from [msdn][1]
(ctx, total) => from order in ctx.SalesOrderHeaders
                        where (total == -1 || order.TotalDue >= total)
                        select order);
  

В SQL вы делаете это, либо используя динамический sql, либо передавая значение по умолчанию (или null), которое указывает, что этот параметр следует игнорировать

 select * from table t
where
    (@age    = 0     or t.age = @age) and
    (@weight is null or t.weight = @weight)