【发布时间】:2009-03-25 05:10:42
【问题描述】:
有没有办法使用 CompiledQuery.Compile 方法来编译与 IQueryable 关联的表达式?目前我有一个 IQueryable,后面有一个非常大的表达式树。 IQueryable 是使用几种方法构建的,每种方法都提供组件。例如,两种方法可能会返回 IQueryables,然后将它们连接到第三个中。由于这个原因,我无法在 compile() 方法调用中明确定义整个表达式。
我希望将表达式作为 someIQueryable.Expression 传递给 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);
}
这种方法可能并不适用于所有情况,但它应该可以帮助任何发现自己处于同样困境的人。
【问题讨论】:
-
您能否提供更多关于您如何整理上述内容的详细信息?这意味着我可以看到您在更新中放入的代码,但是将其放在哪里并且无法编译 - “m.Method.DeclaringType”不是 MemberExpression 的一部分,并且 ExpressionExtensions 不存在。
-
我正在尝试用表达式树操作做类似的事情,并且对上面的实现有几个问题。 ExpressionExpander 和 ExpressionExtensions 在哪里定义?据我所知,它们不是 LinqKit 的一部分。也许它们是旧版本的一部分?
-
确实,代码示例不完整很烦人。 ExpressionExtensions 可能是他自己的类,它覆盖了 VisitMemberAccess,而 m.Method.DeclaringType 可能应该是 m.Member.DeclaringType。但真正的问题是 .GetValue(null, null) 不起作用。
标签: .net performance linq-to-sql expression-trees