有没有办法使用 CompiledQuery.Compile 方法来编译与 IQueryable 关联的表达式?目前我有一个 IQueryable,后面有一个非常大的表达式树。IQueryable 是使用几种方法构建的,每种方法都提供组件。例如,两种方法可能会返回 IQueryables,然后将它们连接到第三个中。由于这个原因,我不能在 compile() 方法调用中明确定义整个表达式。
我希望将表达式作为 someIQueryable.Expression 传递给 compile 方法,但是这个表达式不是 compile 方法所需的形式。如果我尝试通过将查询直接放入 compile 方法来解决此问题,例如:
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
var bar = foo(this);
在数据上下文中进行调用表单的地方,我收到一条错误消息,指出“getUsers 未映射为存储过程或用户定义的函数”。同样,我不能只将 getUsers 方法的内容复制到我进行编译调用的位置,因为它又会使用其他方法。
有什么方法可以将从“getUsers”返回的 IQueryable 上的表达式传递给 Compile 方法?
更新 后我尝试使用以下代码将我的意志强加于系统:
var phony = Expression.Lambda<Func<DataContext, IQueryable<User>>>(
getUsers().Expression, Expression.Parameter(typeof(DataContext), "dc"));
Func<DataContext, IQueryable<User>> wishful = CompiledQuery.Compile<DataContext, IQueryable<User>>(phony);
var foo = wishful(this);
foo 最终成为:
{System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable`1[Model.Entities.User]}
我没有在 foo 中查看结果的选项,因为没有提供扩展结果并运行查询,我只看到消息“操作可能会破坏运行时”。
我只需要找到一种方法让 sql 字符串只生成一次并在后续请求中用作参数化命令,我可以在数据上下文中使用 GetCommand 方法手动执行此操作,但随后我必须显式设置所有参数并自己进行对象映射,考虑到这个特定查询的复杂性,这是几百行代码。
更新
John Rusk 提供了最有用的信息,所以我授予他在这方面的胜利。但是,需要进行一些额外的调整,并且我在此过程中遇到了其他一些问题,所以我想我会“扩展”答案。首先,“操作可能破坏运行时”错误不是由于表达式的编译,它实际上是由于表达式树中的某些深度转换造成的。在某些地方,我需要调用该.Cast<T>()
方法来正式转换项目,即使它们是正确的类型。无需过多详细说明,当几个表达式组合成一棵树并且每个分支都可以返回不同的类型时,这基本上是必需的,它们都是公共类的子类型。
解决了不稳定的问题后,我回到了编译问题。约翰的扩展解决方案几乎就在那里。它在树中查找方法调用表达式,并尝试将它们解析为该方法通常返回的底层表达式。我对表达式的引用不是由方法调用提供的,而是由属性提供的。所以我需要修改执行扩展的表达式访问者以包含这些类型:
protected override Expression VisitMemberAccess(MemberExpression m) {
if(m.Method.DeclaringType == typeof(ExpressionExtensions)) {
return new ExpressionExpander().Visit((Expression)(((System.Reflection.PropertyInfo)m.Member).GetValue(null, null)));
}
return base.VisitMemberAccess(m);
}
这种方法可能并不适用于所有情况,但它应该可以帮助任何发现自己处于同样困境的人。