是否有一个特殊的原因LinqKit的扩展器无法从字段中获取表达式?

Dan*_*mov 26 c# lambda closures expression-trees linqkit

我正在使用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
        };
Run Code Online (Sandbox Code Playgroud)

我们声明了一个投射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()
        )
    );
Run Code Online (Sandbox Code Playgroud)

这很漂亮,因为同样的表达式也可以重用于获取消息列表:

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
    );
Run Code Online (Sandbox Code Playgroud)

如您所见,投影表达式存储在其中_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()
        )
    );
Run Code Online (Sandbox Code Playgroud)

代码按预期工作.

我的问题是:

是否有任何具体原因导致LinqKit无法识别Invoke存储在字段中的表达式?它只是开发人员的遗漏,还是有一些重要的原因,为什么表达式需要首先存储在局部变量中?

这个问题可以通过查看生成的代码和检查LinqKit源来回答,但我想也许与LinqKit开发有关的人可以回答这个问题.

谢谢.

Mic*_*Mic 25

我下载了源代码并尝试分析它.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;
Run Code Online (Sandbox Code Playgroud)

它现在有效.

在这个解决方案中,我之前提到的所有内容(递归遍历树)都是由ExpressionTree编译器为我们完成的:)


Yan*_*kes 10

我创建了Mic答案的改进版本:

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;
Run Code Online (Sandbox Code Playgroud)

主要优点是删除DynamicInvoke很大的开销,Invoke只有在我真正需要它时才会调用.