【问题标题】:Compiling Linq to SQL queries from a non-trivial IQueryable从一个重要的 IQueryable 编译 Linq 到 SQL 查询
【发布时间】: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&lt;T&gt;() 方法来正式转换项目,即使它们是正确的类型。无需过多详细说明,当多个表达式组合成一棵树并且每个分支都可以返回不同的类型(每个都是公共类的子类型)时,这基本上是必需的。

解决了不稳定的问题后,我回到了编译问题。约翰的扩展解决方案几乎就在那里。它在树中查找方法调用表达式,并尝试将它们解析为该方法通常返回的底层表达式。我对表达式的引用不是由方法调用提供的,而是由属性提供的。所以我需要修改执行扩展的表达式访问者以包含这些类型:

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


【解决方案1】:

至少在我的测试中是这样的:

Expression<Func<DataContext, IQueryable<User>> queryableExpression = GetUsers();
var expressionWithSomeAddedStuff = (DataContext dc) => from u in queryableExpression.Invoke(dc) where ....;
var expressionThatCanBeCompiled = expressionWithSomeAddedStuff.Expand();
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(expressionThatCanBeCompiled);

这看起来有点冗长,您可能可以进行一些改进。

他们的关键点是它使用了来自 LinqKit 的 Invoke 和 Expand 方法。它们基本上让您通过组合构建查询,然后编译完成的结果。

【讨论】:

  • 谢谢你,我得去看看。虽然我的方法可以编译,但我不确定它是否真的将表达式树编译为单个函数,或者它是否只是将方法调用烘焙到返回的函数中,这将提供很少的性能改进。如果扩展方法更好,我会将其设置为接受的答案
  • 当我尝试在查询上运行 Expand 方法时,我收到以下错误:无法将类型为“System.Linq.Expressions.MemberExpression”的对象转换为类型“System.Linq.Expressions.LambdaExpression” '
  • 我不是 100% 确定这意味着什么。使用 Invoke 调用的子表达式可能是 MemberExpression 而不是 Lambda 表达式。 (前者对应于“.Something”,而后者对应于“() => ...” - 即它具有 0 个或多个参数的主体。LinqKit 带有完整的源代码。恐怕我只能建议,是不是你通过它调试了......
  • 我使用 Expand 方法发现了问题,它使用的访问者仅覆盖 MethodCallExpression 访问,但因为我提供的表达式实际上是我的数据上下文中的一个字段,我需要覆盖 MemberExpression 访问方法.现在一切正常
猜你喜欢
  • 2014-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多