我正在使用LinqKit库,该库允许即时组合表达式。
对于编写Entity Framework数据访问层来说,这是一种纯粹的幸福,因为可以选择重复使用和组合多个表达式,从而实现既可读又高效的代码。
考虑以下代码片段:
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开发相关的人可以回答这个问题。
谢谢。