Search code examples
c#.netexpression-trees

Error generating Dynamic Query using ExpressionTree


My code is a slight revision from the sample here:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-use-expression-trees-to-build-dynamic-queries

I am writing an extension method that allows for performing unionon any property in the source/destination list and has the following signature

public static IEnumerable<TSource> UnionOn<TSource, TProperty>(
   this IEnumerable<TSource> first, 
   Expression<Func<TSource, TProperty>> expression, 
   IEnumerable<TSource> second)
{

    var finalList = new List<TSource>();
    finalList.AddRange(first);

    var queryableData = finalList.AsQueryable();
    foreach (var item in second.ToList())
    {
        var propertyValue = expression.Compile().Invoke(item);

        // x=>x.ExtendedPropertyId == 'guid_value'
        var sourceObjectParam = Expression.Parameter(typeof(TSource), "x");
        var propertyName = ((MemberExpression)expression.Body).Member.Name;

        var left = Expression.Property(sourceObjectParam, propertyName);
        var right = Expression.Constant(propertyValue);

        var predicateBody = Expression.Equal(left, right);

        MethodCallExpression whereCallExpression = Expression.Call(
            typeof(Enumerable),
            "Where",
            new Type[] { queryableData.ElementType },
            queryableData.Expression,
            Expression.Lambda<Func<TSource, Boolean>>(predicateBody, new ParameterExpression[] { sourceObjectParam }));

        // **** this line causes runtime error *****
        IQueryable<TSource> resultsQuery = queryableData.Provider.CreateQuery<TSource>(whereCallExpression);

        if (resultsQuery.ToList().Any())
            finalList.Add(item);
    }

    return finalList;
}

The exception reads on generating the resultsQuery method:

System.ArgumentException: 'Argument expression is not valid

However, when I look at the debugview of the generated expression in whereCallExpression it looks fine to me:

.Call System.Linq.Enumerable.Where(
.Constant<System.Linq.EnumerableQuery`1[A]>(System.Collections.Generic.List`1[A]),
.Lambda #Lambda1<System.Func`2[A,System.Boolean]>)

.Lambda #Lambda1<System.Func`2[A,System.Boolean]>(A $x) {
$x.ExtendedPropertyId == .Constant<System.Guid>(fadd6b4e-8d97-404c-bcf1- 
 c5ebd02230a6)

}

Any help will be much appreciated.


Solution

  • There are many inefficiencies in this code. And no real benefit of using expressions when working with IEnumerables - Func<..> parameters similar to standard LINQ Enumerable methods would be enough.

    But to answer your concrete question. The exception is because you are calling CreateQuery with expression returning IEnumerable<TSource> (due to Enumerable.Where method call) while it expects (requires) IQueryable (or IQueryable<TSource>) type expression.

    The fix is simple - change typeof(Enumerable) to typeof(Queryable) in your Expression.Call (the other parameters are unchanged) and the problem will be gone.

    Also note that when you have compile time type TSource and IQueryable<TSource> and Expression<Func<TSource, bool>> variables, there is no need to compose Where call and CreateQuery - you could simple use Queryable.Where extension method directly, e.g. given

    var predicate = Expression.Lambda<Func<TSource, bool>>(predicateBody, sourceObjectParam);
    

    the

    MethodCallExpression whereCallExpression = Expression.Call(
        typeof(Queryable),
        "Where",
        new Type[] { queryableData.ElementType },
        queryableData.Expression,
        predicate);
    
    IQueryable<TSource> resultsQuery =  
        queryableData.Provider.CreateQuery<TSource>(whereCallExpression);
    

    can be replaced with

    IQueryable<TSource> resultsQuery = queryableData.Where(predicate);
    

    thus avoiding errors like this.