Компиляция запросов Linq to SQL из нетривиального IQueryable - PullRequest
6 голосов
/ 25 марта 2009

Есть ли способ использовать метод CompiledQuery.Compile для компиляции выражения, связанного с IQueryable? В настоящее время у меня есть IQueryable с очень большим деревом выражений за ним. IQueryable был создан с использованием нескольких методов, каждый из которых предоставляет компоненты. Например, два метода могут возвращать IQueryables, которые затем объединяются в третий. По этой причине я не могу явно определить все выражение в вызове метода compile ().

Я надеялся передать выражение в метод компиляции как someIQueryable.Expression, однако это выражение не в форме, требуемой методом компиляции. Если я попытаюсь обойти это, поместив запрос непосредственно в метод компиляции, например:

    var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
    var bar = foo(this);

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

Есть ли способ передать Expression для 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 [Model.Entities.User]}

У меня нет возможности просмотреть результаты в foo, так как вместо предложения развернуть результаты и выполнить запрос я вижу только сообщение «Операция может дестабилизировать среду выполнения».

Мне просто нужно найти способ, чтобы строка sql генерировалась только один раз и использовалась в качестве параметризованной команды при последующих запросах. Я могу сделать это вручную, используя метод GetCommand для контекста данных, но затем я должен явно установить все параметры и сделать отображение объекта самостоятельно, что составляет несколько сотен строк кода, учитывая сложность этого конкретного запроса.

Обновлено

Джон Раск предоставил самую полезную информацию, поэтому я наградил его победой. Тем не менее, потребовалась некоторая дополнительная настройка, и я столкнулся с парой других проблем, поэтому я решил «развернуть» в ответе. Во-первых, ошибка «Операция могла дестабилизировать среду выполнения» произошла не из-за компиляции выражения, а из-за некоторого приведения глубоко в дерево выражений. В некоторых местах мне нужно было вызывать метод .Cast<T>() для формального приведения предметов, даже если они были правильного типа. Не вдаваясь в подробности, это в основном требовалось, когда несколько выражений были объединены в одно дерево, и каждая ветвь могла возвращать свой тип, каждый из которых был подтипом общего класса.

После решения проблемы дестабилизации я вернулся к проблеме компиляции. Расширенное решение Джона было почти там. Он искал выражения вызова метода в дереве и пытался разрешить их в базовом выражении, которое метод обычно возвращает. Мои ссылки на выражения были предоставлены не вызовами методов, а свойствами. Поэтому мне нужно было изменить посетитель выражения, который выполняет расширение, чтобы включить следующие типы:

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

Этот метод может быть не уместным во всех случаях, но он должен помочь любому, кто окажется в том же затруднительном положении.

1 Ответ

2 голосов
/ 05 мая 2009

Примерно так работает, по крайней мере, в моих тестах:

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. Они в основном позволяют вам составить запрос с помощью композиции, а затем скомпилировать готовый результат.

...