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

我正在使用LinqKit库,它允许动态组合表达式。

这是编写Entity Framewok数据访问层的纯粹幸福,因为可以选择重复使用和组合多个表达式,这样就可以实现可读和高效的代码。

考虑以下代码:

private static readonly Expression<Func> _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.FieldExpression转换为System.Linq.Expressions.LambdaExpression

只是过了一段时间,我意识到在调用Invoke on之前,在局部变量中引用表达式时一切正常

 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开发有关的人可以回答这个问题。

谢谢。

我下载了源代码并尝试分析它。 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编译器为我们完成的:)

我创建了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>(input).Compile()()); return input; 

主要优点是删除具有很大开销的 DynamicInvoke ,并且仅在我真正需要时才调用Invoke