Evaluator.PartialEval减少提供的表达式

时间:2017-06-08 09:41:53

标签: c# entity-framework linq lambda expression-trees

在我的一个项目中,我有一个ExpressionVisitor将提供的表达式转换为某个查询字符串。但在翻译之前,我需要将表达式中的所有参考值都评估为实际值。为此,我使用EntityFramework Project中的Evaluator.PartialEval方法。

假设我有这个问题:

 var page = 100;
 var query = myService.AsQueryable<Product>()
              //.Where(x=>x.ProductId.StartsWith(p.ProductId))
                .Skip(page)
                .Take(page);

var evaluatedQueryExpr = Evaluator.PartialEval(query.Expression);

正如您所看到的,我已经评论了Where方法。在这种情况下,evaluateQueryExpr将不包含Take和Skip方法。

但是,如果我在Take或Skip之前使用Expression的任何其他方法,则Evaluator会正确评估表达式并将其完全返回。

我发现问题发生在Evaluator类的第80行:

return Expression.Constant(fn.DynamicInvoke(null), e.Type);

你能解释一下为什么会发生这种情况并提出一个解决方法吗?

更新 这是a project on github

LinqToSolrQueriable inherited from IOrderedQueryable LinqToSolrProvider inherited from IQueryProvider包括导致问题的行范围

1 个答案:

答案 0 :(得分:2)

好消息是表达式并没有真正减少(SkipTake仍然存在:),但只是从MethodCallExpression转换为ConstantExpression 包含原始表达式:

query.Expression

.Call System.Linq.Queryable.Take(
    .Call System.Linq.Queryable.Skip(
        .Constant<LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product]>(LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product]),
        100),
    100)

evaluatedQueryExpr:

.Constant<System.Linq.IQueryable`1[LinqToSolrTest.Product]>(LinqToSolr.Query.LinqToSolrQueriable`1[LinqToSolrTest.Product])

此处调试显示给您一个错误的印象。如果您使用ConstaintExpression.Value,则会看到IQueryable<Product> Expression属性与原始query.Expression完全相同。

坏消息是,这不是你对PartialEval的期望 - 事实上它在这种情况下没有做任何有用的事情(除了可能破坏你的查询翻译逻辑)。

那么为什么会这样呢?

您从EntityFramework.Extended库中使用的方法依次来自MSDN示例Walkthrough: Creating an IQueryable LINQ Provider(如评论中所示)。可以注意到PartialEval方法有两个重载 - 一个用Func<Expression, bool> fnCanBeEvaluated参数来识别给定表达式节点是否可以成为本地函数的一部分(换句话说) ,部分评估或未部分评估),以及没有这样的参数(由您使用),它只是调用第一个传递以下谓词:

private static bool CanBeEvaluatedLocally(Expression expression)
{
    return expression.NodeType != ExpressionType.Parameter;
}

效果是它会停止评估ParameterExpression类型表达式以及任何直接或间接包含的表达式ParameterExpression。最后一个应该解释你正在观察的行为。当查询在Where / {{1}之前包含带有参数化lambda表达式(因此参数)的Skip(以及基本上任何LINQ运算符)时}调用,它将停止评估包含的方法(您可以从上面的Take调试视图中看到 - query.Expression调用将在Where内。

现在,MSDN示例使用此重载来评估具体嵌套 Skip方法lambda表达式,并且通常不适用于任何类型的表达式,如Where。事实上,链接的项目在QueryCache类中的单个位置使用IQueryable.Expression方法,并且还调用另一个重载传递different predicate除了PartialEval之外还停止了评估结果类型为ParameterExpressions的任何表达式。

我认为这也是你问题的解决方案:

IQueryable