LinqKit';s扩展器可以';t从字段中拾取表达式

本文关键字:字段 表达式 扩展器 LinqKit | 更新日期: 2023-09-27 17:58:23

我使用的是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开发相关的人可以回答这个问题。

谢谢。

LinqKit';s扩展器可以';t从字段中拾取表达式

我下载了源代码并尝试对其进行分析。ExpressionExpander不允许引用存储在常量以外的变量中的表达式。它期望表达式Invoke方法被调用以引用由ConstantExpression表示的对象,而不是另一个MemberExpression

因此,我们不能提供可重用的表达式作为对类的任何成员的引用(甚至是公共字段,而不是属性)。嵌套成员访问(如object.member1.member2…等)也不受支持。

但这可以通过遍历初始表达式和重新提取子字段值来解决。

我已经将ExpressionExpander类的TransformExpr方法代码替换为

var lambda = Expression.Lambda(input);
object value = lambda.Compile().DynamicInvoke();
if (value is Expression)
    return Visit((Expression)value);
else
    return input;

现在它起作用了。

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

我创建了改进版的麦克风应答:

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