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
。