Компиляция Linq в SQL-запросы из нетривиального IQueryable
-
22-08-2019 - |
Вопрос
Есть ли способ использовать CompiledQuery.Метод Compile для компиляции выражения, связанного с IQueryable?В настоящее время у меня есть IQueryable с очень большим деревом выражений за ним.IQueryable был создан с использованием нескольких методов, каждый из которых предоставляет компоненты.Например, два метода могут возвращать IQueryables, которые затем объединяются в третий.По этой причине я не могу явно определить все выражение в вызове метода compile().
Я надеялся передать выражение в метод компиляции как someIQueryable.Выражение, однако это выражение не в форме, требуемой методом компиляции.Если я попытаюсь обойти это, поместив запрос непосредственно в метод компиляции, например:
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
var bar = foo(this);
когда я создаю форму вызова внутри datacontext, я получаю сообщение об ошибке, в котором говорится, что "getUsers не отображается как хранимая процедура или определяемая пользователем функция".Опять же, я не могу просто скопировать содержимое метода getUsers туда, где я выполняю вызов compile, поскольку он, в свою очередь, использует другие методы.
Есть ли какой-нибудь способ передать выражение для IQueryable, возвращаемое из "getUsers", в метод Compile?
Обновленный Я попытался навязать системе свою волю с помощью следующего кода:
var phony = Expression.Lambda<Func<DataContext, IQueryable<User>>>(
getUsers().Expression, Expression.Parameter(typeof(DataContext), "dc"));
Func<DataContext, IQueryable<User>> wishful = CompiledQuery.Compile<DataContext, IQueryable<User>>(phony);
var foo = wishful(this);
foo в конечном итоге становится:
{System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable`1[Модель.Сущности.Пользователь]}
У меня нет возможности просмотреть результаты в foo, так как вместо предложения развернуть результаты и запустить запрос я вижу только сообщение "Операция может дестабилизировать среду выполнения".
Мне просто нужно найти способ, чтобы строка sql генерировалась только один раз и использовалась в качестве параметризованной команды при последующих запросах, я могу сделать это вручную, используя метод getCommand в контексте данных, но тогда мне придется явно задать все параметры и самому выполнить сопоставление объектов, что составляет несколько сотен строк кода, учитывая сложность этого конкретного запроса.
Обновленный
Джон Раск предоставил самую полезную информацию, поэтому я присудил ему победу в этом деле.Однако потребовалась некоторая дополнительная настройка, и по пути я столкнулся с парой других проблем, поэтому я решил "Расширить" ответ.Во-первых, ошибка "Операция может дестабилизировать время выполнения" была вызвана не компиляцией выражения, а фактически некоторым приведением глубоко в дереве выражений.В некоторых местах мне нужно было позвонить в .Cast<T>()
метод формального приведения элементов, даже если они были правильного типа.Не вдаваясь в излишние подробности, это в основном требовалось, когда несколько выражений были объединены в одно дерево и каждая ветвь могла возвращать другой тип, каждый из которых был подтипом общего класса.
Решив проблему с дестабилизацией, я вернулся к проблеме с компиляцией.Решение Джона по расширению было почти готово.Он искал выражения вызова метода в дереве и пытался преобразовать их в базовое выражение, которое обычно возвращал бы метод.Мои ссылки на выражения были предоставлены не вызовами методов, а вместо этого свойствами.Поэтому мне нужно было изменить выражение visitor, которое выполняет расширение, чтобы включить эти типы:
protected override Expression VisitMemberAccess(MemberExpression m) {
if(m.Method.DeclaringType == typeof(ExpressionExtensions)) {
return new ExpressionExpander().Visit((Expression)(((System.Reflection.PropertyInfo)m.Member).GetValue(null, null)));
}
return base.VisitMemberAccess(m);
}
Этот метод может быть уместен не во всех случаях, но он должен помочь любому, кто оказался в таком же затруднительном положении.
Решение
Что-то подобное работает, по крайней мере, в моих тестах:
Expression<Func<DataContext, IQueryable<User>> queryableExpression = GetUsers();
var expressionWithSomeAddedStuff = (DataContext dc) => from u in queryableExpression.Invoke(dc) where ....;
var expressionThatCanBeCompiled = expressionWithSomeAddedStuff.Expand();
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(expressionThatCanBeCompiled);
Это выглядит немного многословно, и, вероятно, есть улучшения, которые вы можете внести.
Ключевым моментом является то, что он использует методы Invoke и Expand из LinqKit.По сути, они позволяют вам создать запрос с помощью композиции, а затем скомпилировать готовый результат.