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