我正在使用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开发有关的人可以回答这个问题。
感谢。
答案 0 :(得分: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;
现在有效。
在此解决方案中,我之前提到的所有内容(递归遍历树)都是由ExpressionTree
编译器为我们完成的:)
答案 1 :(得分:10)
我创建了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
,只有在我真正需要时才会调用{{1}}。