从非平凡的IQueryable编译Linq到SQL查询

时间:2009-03-25 05:10:42

标签: .net performance linq-to-sql expression-trees

有没有办法使用CompiledQuery.Compile方法编译与IQueryable相关联的Expression?目前我有一个IQueryable,后面有一个非常大的表达式树。 IQueryable是使用几种方法构建的,每种方法都提供组件。例如,两个方法可以返回IQueryables,然后在第三个方法中连接。出于这个原因,我无法在compile()方法调用中显式定义整个表达式。

我希望将表达式作为someIQueryable.Expression传递给compile方法,但是这个表达式不是compile方法所需的形式。如果我尝试通过将查询直接放入编译方法来解决这个问题,例如:

    var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
    var bar = foo(this);

我在datacontext中创建调用表单,我得到一个错误,说“getUsers未映射为存储过程或用户定义的函数”。我再次不能将getUsers方法的内容复制到我进行编译调用的地方,因为它反过来使用其他方法。

有没有办法将从“getUsers”返回的IQueryable上的Expression传递给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字符串只生成一次并用作后续请求的paramaterized命令,我可以在数据上下文中使用GetCommand方法手动完成,但是我必须明确设置所有的参数和我自己做的对象映射,这是几百行代码给出这个特定查询的复杂性。

更新

约翰·拉斯克提供了最有用的信息,所以我在这个上获得了胜利。然而,需要进行一些额外的调整,并且在此过程中我遇到了一些其他问题,所以我认为我会“扩大”答案。首先,'操作可能会使运行时不稳定'错误不是由于表达式的编译,实际上是因为表达式树中有一些深入。在某些地方,我需要调用.Cast<T>()方法来正式投射项目,即使它们的类型正确。在没有详细介绍的情况下,基本上需要将几个表达式组合到一个树中,并且每个分支都可以返回不同的类型,这些类型都是公共类的子类型。

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

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);
}

这种方法可能并不适用于所有情况,但它应该帮助任何发现自己陷入同样困境的人。

1 个答案:

答案 0 :(得分:2)

这样的事情起作用,至少在我的测试中是这样的:

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方法。它们基本上允许您通过组合构建查询,然后编译完成的结果。