5

I'm trying to dynamically construct an expression similar to the one below, where I can use the same comparison function, but where the values being compared can be passed in, since the value is passed from a property 'higher-up' in the query.

var people = People
    .Where(p => p.Cars
        .Any(c => c.Colour == p.FavouriteColour));

I believe I've constructed the query correctly, but the ExpressionExpander.VisitMethodCall(..) method throws the following exception when I try to use it:

"Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'"

In real-world code, using Entity Framework and actual IQueryable<T>, I often get:

"Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'" as well.

I've constructed a LinqPad-friendly example of my problem, as simple as I could make it.

void Main()
{
    var tuples = new List<Tuple<String, int>>() {
        new Tuple<String, int>("Hello", 4),
        new Tuple<String, int>("World", 2),
        new Tuple<String, int>("Cheese", 20)
    };

    var queryableTuples = tuples.AsQueryable();

    // For this example, I want to check which of these strings are longer than their accompanying number.
    // The expression I want to build needs to use one of the values of the item (the int) in order to construct the expression.
    // Basically just want to construct this:
    //      .Where (x => x.Item1.Length > x.Item2)

    var expressionToCheckTuple = BuildExpressionToCheckTuple();

    var result = queryableTuples
        .AsExpandable()
        .Where (t => expressionToCheckTuple.Invoke(t))
        .ToList();
}

public Expression<Func<string, bool>> BuildExpressionToCheckStringLength(int minLength) {

    return str => str.Length > minLength;

}

public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {

    // I'm passed something (eg. Tuple) that contains:
    //  * a value that I need to construct the expression (eg. the 'min length')
    //  * the value that I will need to invoke the expression (eg. the string)

    return tuple => BuildExpressionToCheckStringLength(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);

}

If I'm doing something obviously wrong, I'd really appreciate a nudge in the right direction! Thanks.


Edit: I know that the following would work:

Expression<Func<Tuple<string, int>, bool>> expr = x => x.Item1.Length > x.Item2;

var result = queryableTuples
    .AsExpandable()
    .Where (t => expr.Invoke(t))
    .ToList();

However, I'm trying to separate the comparison from the location of the parameters, since the comparison could be complex and I would like to re-use it for many different queries (each with different locations for the two parameters). It is also intended that one of the parameters (in the example, the 'min length') would actually be calculated via another expression.


Edit: Sorry, I've just realised that some answers will work when attempted against my example code since my example is merely masquerading as an IQueryable<T> but is still a List<T> underneath. The reason I'm using LinqKit in the first place is because an actual IQueryable<T> from an EntityFramework DbContext will invoke Linq-to-SQL and so must be able to be parsed by Linq-to-SQL itself. LinqKit enables this by expanding everything to expressions.


Solution! Thanks to Jean's answer below, I think I've realised where I'm going wrong.

If a value has come from somewhere in the query (i.e. not a value that is known before-hand.) then you must build the reference/expression/variable to it into the expression.

In my original example, I was trying to pass the 'minLength' value taken from within the expression and pass it to a method. That method call could not be done before-hand, since it used a value from the expression, and it could not be done within the expression, since you can't build an expression within an expression.

So, how to get around this? I chose to write my expressions so that they can be invoked with the additional parameters. Though this has the downside that the parameters are no longer 'named' and I could end up with an Expression<Func<int, int, int, int, bool>> or something down the line.

// New signature.
public Expression<Func<string, int, bool>> BuildExpressionToCheckStringLength() {

    // Now takes two parameters.
    return (str, minLength) => str.Length > minLength;

}

public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {

    // Construct the expression before-hand.
    var expression = BuildExpressionToCheckStringLength();

    // Invoke the expression using both values.     
    return tuple => expression.Invoke(tuple.Item1 /* string */, tuple.Item2 /* the length */);

}
Community
  • 1
  • 1
Ben Jenkinson
  • 1,806
  • 1
  • 16
  • 31
  • Why do you need to use `AsExpandable()` and `Expression – sgmoore May 13 '14 at 13:05
  • `AsExpandable()` is the main magic of [LinqKit](http://www.albahari.com/nutshell/linqkit.aspx), everything needs to be an Expression so that it can be compiled directly to SQL later on. – Ben Jenkinson May 13 '14 at 13:20
  • Is the `Invoke` you use an extension method? From LinqKit? – Jean Hominal May 13 '14 at 13:21
  • Yes, `Invoke` is a helper method that expands to `.Compile().Invoke(arg1, arg2..)` which satisfies the compiler. At runtime, `AsExpandable()` uses a custom `ExpressionVisitor` called `LinqKit.ExpressionExpander` to remove all calls to `Invoke` and replace them with an Expression tree rather than a compiled function. (I don't fully comprehend how it works, that's why I'm here, but I think that's roughly it.) LinqKit's homepage is here: http://www.albahari.com/nutshell/linqkit.aspx or you can view the source on Github: https://github.com/scottksmith95/LINQKit – Ben Jenkinson May 13 '14 at 13:50

2 Answers2

0

So you are looking for something like this:

public static class Program
    {
        public class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

        public static IQueryable<T> WherePropertyEquals<T, TProperty>(
            this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
        {
            var result = src.Where(e => property.Invoke(e).Equals(value));
            return result;
        }

        public static IQueryable<T> WhereGreater<T, TProperty>(
            this IQueryable<T> src, Expression<Func<T, TProperty>> property, TProperty value)
            where TProperty : IComparable<TProperty>
        {
            var result = src.Where(e => property.Invoke(e).CompareTo(value) > 0);
            return result;
        }

        public static IQueryable<T> WhereGreater<T, TProperty>(
            this IQueryable<T> src, Expression<Func<T, TProperty>> left, Expression<Func<T, TProperty>> right)
            where TProperty : IComparable<TProperty>
        {
            var result = src.Where(e => left.Invoke(e).CompareTo(right.Invoke(e)) > 0);
            return result;
        }

        public static void Main()
        {
            var persons = new List<Person>()
                {
                    new Person
                        {
                            FirstName = "Jhon",
                            LastName = "Smith"
                        },
                    new Person
                        {
                            FirstName = "Chuck",
                            LastName = "Norris"
                        },
                    new Person
                        {
                            FirstName = "Ben",
                            LastName = "Jenkinson"
                        },
                    new Person
                        {
                            FirstName = "Barack",
                            LastName = "Obama"
                        }
                }
                .AsQueryable()
                .AsExpandable();

            var chuck = persons.WherePropertyEquals(p => p.FirstName, "Chuck").First();
            var ben = persons.WhereGreater(p => p.LastName.Length, 6).First();
            var barack = persons.WhereGreater(p => p.FirstName.Length, p => p.LastName.Length).First();
        }
Vitaliy Kalinin
  • 1,791
  • 12
  • 20
  • Unfortunately, I'm trying to achieve the separation between the expression that contains the comparison, and the expression that indicates where the parameter to be compared is located. The use case is that I have several different queries that will need to make the same (convoluted) check and would like to pull it out into an expression; each case would be deriving its input values from a different location, depending on the items being queried. – Ben Jenkinson May 13 '14 at 12:29
  • I have updated my answer. You are looking for something like this? – Vitaliy Kalinin May 13 '14 at 13:03
  • Not quite. That's similar to what I started with. If you adjust your Person class to have a `ICollection` property called 'Children', then try to find items from the root list of `Person` based upon whether they have any children that match the predicate? You can't write them as extension methods to `IQueryable` because your query is hanging off an `ICollection` property and Linq-to-SQL can't handle extension methods at all. – Ben Jenkinson May 13 '14 at 14:06
  • I did just try your example code, and it works fine querying my example because although it's masquerading as an `IQueryable` it's still actually a `List` and Linq-To-SQL is not actually involved. I don't think it will work against an EntityFramework DbContext. – Ben Jenkinson May 13 '14 at 14:12
  • So try it against EF. Actually if second generic parameter of WhereGreater can be eliminated (which means that you will know beforehand values of which types should be compared) then implementation can be simplified and there is good chances that LinqKit will build expression tree recognizable for EF. – Vitaliy Kalinin May 13 '14 at 14:34
  • I adjusted yours as I described and I was right, Linq-to-SQL can't handle extension methods. I'm just writing up a more detailed example. I think I made the original one too simple. – Ben Jenkinson May 13 '14 at 14:51
  • "LINQ to Entities does not recognize the method 'X' method, and this method cannot be translated into a store expression." It's the standard error when you're doing something that doesn't have an equivalent in SQL. One of the main features of LinqKit is to provide a way around such an exception. – Ben Jenkinson May 13 '14 at 16:24
0

OK, so what you are trying to do (the transformation from a function that takes a single argument, that returns another function that takes a single argument f(x)(y) into a function that takes two arguments f(x, y)) is known as uncurrying. Look it up! :)

Now, the issue that you have in your code is that, in the expression returned by BuildExpressionToCheckTuple, there is a method call to BuildExpressionToCheckStringLength, which is not resolved. And you cannot resolve it because it takes an argument that is embedded in the tuple parameter.

The solution is, instead of using a method call, to use a lambda expression that will be equivalent to that method call.

That is:

public Expression<Func<int, Func<string, bool>>> ExpressionToCheckStringLengthBuilder() {
    return minLength =>
        str => str.Length > minLength;
}

public Expression<Func<Tuple<string, int>, bool>> BuildExpressionToCheckTuple() {
    // I'm passed something (eg. Tuple) that contains:
    //  * a value that I need to construct the expression (eg. the 'min length')
    //  * the value that I will need to invoke the expression (eg. the string)

    // Putting builder into a variable so that the resulting expression will be 
    // visible to tools that analyze the expression.
    var builder = ExpressionToCheckStringLengthBuilder();

    return tuple => builder.Invoke(tuple.Item2 /* the length */).Invoke(tuple.Item1 /* string */);
}
Jean Hominal
  • 16,518
  • 5
  • 56
  • 90