8

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.

Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158

1 Answers1

9

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

Servy
  • 202,030
  • 26
  • 332
  • 449
  • This method might have a few parameters, none of which are used in the query. There is no way to define parametrized properties in C#. But you are right in that I have not made that clear in my question. Let me update that. – Andrew Savinykh Nov 04 '14 at 20:18
  • @zespri That problem is solvable, although it has an additional restriction. – Servy Nov 04 '14 at 20:23
  • Compiling a lambda for every query that is being issued will cause high CPU load. How much time does compiling a lambda take? 1ms? – usr Nov 04 '14 at 20:32
  • @usr I fail to see any other possible solutions to the problem, as stated. You could avoid putting yourself in that position, but given that you have code that you want to execute to a value defined in an `Expression` you'll have to compile it. Performance is only really worth commenting on when there are viable alternatives. Additionally, you're comparing those times against the network requests that are going to be made with the generated query. It's going to pale in comparison to that. As a rule you rarely need to consider performance when dealing with `Expression` for that reason. – Servy Nov 04 '14 at 20:34
  • I have a proxy LINQ provider in production that handles this case. It has a cache bucket per unique query structure. Client-side expressions that return IQueryable are indeed evaluated and inlined. But the compiled lambda is cached as part of the cache bucket. After inlining the query we take another round through the cache. The reason I wrote this is because query compilation in L2S is rather CPU intensive. Needed to insert a transparent cache. – usr Nov 04 '14 at 20:39
  • @usr For the cache to really be meaningful you'd need a way of comparing expressions based on their value, rather than based on their reference, as well as generating a meaningful hash of an expression. That's not a very easy problem to solve. – Servy Nov 04 '14 at 20:43
  • It is solvable, though. Needs a custom expression comparer. Generating the hash is not a hard problem, either, using the usual xor tricks. – usr Nov 04 '14 at 20:51
  • @usr As I said, it's hard, not impossible. It's also *expensive*. Doing a complete comparison of entire expression trees when you have a large number of large trees can start adding up fast, even with a decent enough hash algorithm. As for the hash, generating something that meets the technical contract required is easy enough; writing one that does a good job of minimizing collisions is much harder. – Servy Nov 04 '14 at 20:53
  • Yes, the dictionary lookup is the majority of the work. It still reduces the L2S CPU overhead by 10x or so. A simple get-by-PK query can take .1ms in ADO.NET and .5ms in L2S. – usr Nov 04 '14 at 21:15
  • If you know that your `Expression target` is a `MethodCallExpression` can you collect the arguments and invoke it without wrapping it in Lambda and calling `Compile()`? (Thus avoiding high CPU usage the `Compile` would cause) – Andrew Savinykh Nov 05 '14 at 21:17
  • @zespri You're effectively talking about writing your own compiler at that point. You just aren't going to accomplish that much faster than the existing compiler is. I also expect that you're overestimating the costs of compiling the lambda in the vast majority of situations. We're talking about a fraction of a millisecond here. Yes, there are times where that matters, even when that time is spent just before a network request that will take dozens of milliseconds in the best of cases, but those situations are somewhat rare. – Servy Nov 05 '14 at 21:23
  • @zespri Rather than try to spend a huge amount of effort trying to optimize for a very few cases in which the costs associated with this method are a problem, I would personally just use the code as is and, if it wasn't good enough, avoid using LINQ entirely. If these costs really are too much, then, at least for that particularly query, you just need to remove all of the overhead and build the SQL yourself (or use a stored proc). You also have the option, for the specific problem mentioned in the question, of moving the function call out of the expression in those few cases that matter. – Servy Nov 05 '14 at 21:25