Есть ли особая причина, по которой расширитель LinqKit не может получать выражения из полей? - PullRequest
26 голосов
/ 03 июня 2011

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

Это чистое блаженство для записи слоя доступа к данным Entity Framewok, поскольку несколько выражений можно по выбору использовать и комбинировать, что позволяет как для читаемого, так и для эффективного кода.

Рассмотрим следующий фрагмент кода:

private static readonly Expression<Func<Message, int, MessageView>> _selectMessageViewExpr =
    ( Message msg, int requestingUserId ) =>
        new MessageView
        {
            MessageID = msg.ID,
            RequestingUserID = requestingUserId,
            Body = ( msg.RootMessage == null ) ? msg.Body : msg.RootMessage.Body,
            Title = ( ( msg.RootMessage == null ) ? msg.Title : msg.RootMessage.Title ) ?? string.Empty
        };

Мы объявляем выражение, которое проецирует Message на MessageView (я удалил детали для ясности).

Теперь код доступа к данным может использовать это выражение для получения отдельного сообщения:

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageView",
    () => CompiledQuery.Compile(
        _getMessagesExpr
            .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) ) // re-use the expression
            .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id )
            .Expand()
        )
    );

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

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageViewList",
    () => CompiledQuery.Compile(
        BuildFolderExpr( folder )
            .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) )
            .OrderBy( mv => mv.DateCreated, SortDirection.Descending )
            .Paging()
            .Expand()
        ),
    folder
    );

Как видите, выражение проекции хранится в _selectMessageViewExpr и используется для построения нескольких различных запросов.

Однако я потратил много времени на поиск странной ошибки, где этот код был сбой при Expand() вызове .
В сообщении об ошибке:

Невозможно привести объект типа System.Linq.Expressions.FieldExpression к типу System.Linq.Expressions.LambdaExpression.

Только через некоторое время я понял, что все работает, когда на выражение ссылаются в локальной переменной перед вызовом Invoke в :

var selector = _selectMessageViewExpr; // reference the field

var query = CompiledQueryCache.Instance.GetCompiledQuery(
    "GetMessageView",
    () => CompiledQuery.Compile(
        _getMessagesExpr
            .Select( msg => selector.Invoke( msg, userId ) ) // use the variable
            .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id )
            .Expand()
        )
    );

Это код работает должным образом.

Мой вопрос:

Есть ли какая-либо конкретная причина, по которой LinqKit не распознает Invoke в выражениях, хранящихся в полях? Это просто упущение разработчика, или есть какое-товеская причина, по которой выражения нужно сначала хранить в локальных переменных?

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

Спасибо.

Ответы [ 2 ]

25 голосов
/ 18 октября 2011

Я скачал исходный код и попытался проанализировать его.ExpressionExpander не позволяет ссылаться на выражения, которые хранятся в переменных, отличных от константы.Ожидается выражение, что метод Invoke вызывается для ссылки на объект, представленный ConstantExpression, а не на другой MemberExpression.

. Поэтому мы не можем предоставить наше повторно используемое выражение в качестве ссылки на какой-либо член класса (даже публичные поля, а не свойства).Доступ к вложенным элементам (например, object.member1.member2 ... и т. Д.) Также не поддерживается.

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

Я заменил TransformExpr код метода класса ExpressionExpander на

var lambda = Expression.Lambda(input);
object value = lambda.Compile().DynamicInvoke();

if (value is Expression)
    return Visit((Expression)value);
else
    return input;

и теперь это работает.

В этом решении все, что я упомянул ранее (рекурсивное дерево обхода), сделано для нас ExpressionTree compiler:)

10 голосов
/ 15 ноября 2013

Я создал улучшенную версию Mic answer :

if (input == null)
    return input;

var field = input.Member as FieldInfo;
var prope = input.Member as PropertyInfo;
if ((field != null && field.FieldType.IsSubclassOf(typeof(Expression))) ||
    (prope != null && prope.PropertyType.IsSubclassOf(typeof(Expression))))
    return Visit(Expression.Lambda<Func<Expression>>(input).Compile()());

return input;

Основным преимуществом является удаление DynamicInvoke, у которого большие накладные расходы и вызов Invoke только тогда, когда он мне действительно нужен.

...