Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to inline external functions into an EF Linq query?

Let say I have a function like this:

var filterValue = GetCurrentFilter(state);

And then an EF query:

var result = context.EntitySet.Where(x=> x.column > filterValue);

this works, but as soon as I try to inline that:

var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state));

It does not because EF Linq tried to parse GetCurrentFilter into expression tree and is unable to do that. This is all quite understandable.

My question is, is there a way to let EF Linq know that in needs to execute the GetCurrentFilter function when it builds the tree and use its result in the tree?

Something like

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)));

Since GetCurrentFilter does not have parameters that is a part of the query this should be technically possible to do that if EF Linq can support it that is. I'm suspecting that I'm just missing the correct syntax for that.

like image 710
Andrew Savinykh Avatar asked Nov 04 '14 20:11

Andrew Savinykh


1 Answers

Make GetCurrentFilter a (read only) property instead of a method. EF will evaluate properties to their values, rather than trying to translate them into SQL, unlike methods.


The only other road that you have is to traverse the entire expression tree, search for usage of your ResultOf method, evaluate its parameter to a value, and then inline that value where the ResultOf call once was, rebuiding the query around that value.

In order for this to work it means you need to not only wrap the code you want to inline in a call to EfUtil.ResultOf, but it also means calling a method on the query itself to force it to go back and evaluate it:

public class EfUtil
{
    public static T ResultOf<T>(T value)
    {
        return value;
    }
}
//Note this could probably use a better name
public static IQueryable<T> EvaluateResults<T>(this IQueryable<T> query)
{
    return query.Provider.CreateQuery<T>(
        new ExpressionEvaluator().Visit(query.Expression));
}

internal class ExpressionEvaluator : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression m)
    {
        if (m.Method.Name == "ResultOf" && m.Method.DeclaringType == typeof(EfUtil))
        {
            Expression target = m.Arguments[0];

            object result = Expression.Lambda(target)
                .Compile()
                .DynamicInvoke();

            return Expression.Constant(result, target.Type);
        }
        else
            return base.VisitMethodCall(m);
    }
}

This would allow you to write:

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state)))
    .EvaluateResults();

It would then evaluate GetCurrentFilter(state) on the client side and inline the result as a constant into the query.

As a slightly simpler test, we can write the following:

var query = new[] { 1, 2, 3 }
    .AsQueryable()
    .Where(x => x > EfUtil.ResultOf(Math.Max(1, 2)))
    .EvaluateResults();

Console.WriteLine(query.ToString());

And it will print out:

System.Int32[].Where(x => (x > 2))

Which is exactly what we want.

Note that the use of the lambda's parameter (x in these examples) cannot be used anywhere within the call to EfUtil.ResultOf or the code won't work, and couldn't possibly be made to work (although we could generate a better error message if we cared enough).

like image 58
Servy Avatar answered Oct 17 '22 09:10

Servy