Evaluator.PartialEval decrements the supplied expression

In one of my projects, I have an ExpressionVisitor to translate a provided expression into some query string. But before translating it, I need to evaluate all repression in terms of real values. For this I am using the Evaluator.PartialEval method from the EntityFramework project.

Assuming I have this request:

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

      

As you can see, I commented out the Where method. In this case, the evaluatedQueryExpr will not contain the Take and Skip methods.

However, if I use any other method with the expression before executing or skipping everything works, the Evaluator evaluates the expression correctly and returns it in full.

I found out that the problem occurs on line 80 of the Evaluator class:

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

      

Could you please explain why this is happening and suggest a workaround?

Update here project on github

LinqToSolrQueriable inherited from IOrderedQueryable LinqToSolrProvider inherited from IQueryProvider including linear range causing issue

+3


source to share


1 answer


The good news is that the expression is not really minified ( Skip

and Take

still is :), but simply converted from MethodCallExpression

to ConstantExpression

containing the original expression:

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])

      

Here the debug display is giving you the wrong impression. If you take ConstaintExpression.Value

, you will see that the IQueryable<Product>

c property Expression

is the same as the original query.Expression

.

The bad news is that this is not what you expect from it PartialEval

- in fact, in this case, it does nothing useful (other than potentially breaking the query translation logic).



So why is this happening?

The method you are using from EntityFramework.Extended is in turn taken (as pointed out in the comments) from the MSDN example Walkthrough. Creating an IQueryable LINQ Provider . You may notice that the method PartialEval

has two overloads - one with a parameter Func<Expression, bool> fnCanBeEvaluated

used to determine if a given node expression can be part of a local function (in other words, partially evaluated or not) and one without such a parameter (used by you), which just calls the first pass of the following predicate:

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

      

The effect is that it stops the evaluation of type expressions ParameterExpression

and any expressions containing directly or indirectly ParameterExpression

. The latter should explain the behavior that you observe. When a query contains Where

(and basically any LINQ statement) with a parameterized lambda expression (hence a parameter) before calls to Skip

/ Take

, it will stop evaluating the contained methods (as you can see from the above query.Expression

debug view - the call Where

will be inside Skip

).

This overload is now used by the MSDN example to evaluate a specific nested lambda method Where

expression, and is generally not applicable to any type of expression such as IQueryable.Expression

. The actually linked project uses a method PartialEval

in one place within the QueryCache class and also invokes another overload, passing through another predicate , which in addition to ParameterExpressions

stops evaluating any expression with a result type IQueryable

.

I think this is the solution to your problem:

var evaluatedQueryExpr = Evaluator.PartialEval(query.Expression,
    // can't evaluate parameters or queries
    e => e.NodeType != ExpressionType.Parameter &&
        !typeof(IQueryable).IsAssignableFrom(e.Type)
);

      

+2


source







All Articles