Search code examples
c#linqexpression-trees

Build Any() with Expression Trees for LINQ Queries


I'm building a SQL "Any" clause dynamically using the System.Linq.Expressions.Expression class

I can do it like this

Expression<Func<User, Lead, bool>> predicate = (user, lead) => user.UserRoleSubProducts.Any(x => x.SubProductID == lead.SubProductID);

But I am not able to achieve this using Expression Tree.

I have tried below

var param1 = Expression.Parameter(typeof(User), "user");
var property1 = Expression.Property(param1, "UserRoleSubProducts");
var exp1 = Expression.Lambda(property1, new[] { param1 });

var param2 = Expression.Parameter(typeof(Lead), "lead");
var property2 = Expression.Property(param2, "SubProductID");
var exp2 = Expression.Lambda(property2, new[] { param2 });

var param3 = Expression.Parameter(property1.Type.GetProperty("Item").PropertyType, "x");
var property3 = Expression.Property(param3, "SubProductID");
var exp3 = Expression.Lambda(property3, new[] { param3 });

var equality = Expression.Equal(property2, property3);

var any = typeof(Queryable).GetMethods().Where(m => m.Name == "Any").Single(m => m.GetParameters().Length == 2).MakeGenericMethod(property1.Type);

var expression = Expression.Call(null, any, property1, equality);

But getting

Expression of type 'Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]' cannot be used for parameter of type System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]]' of method 'Boolean Any[DataServiceCollection1](System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]], System.Linq.Expressions.Expression1[System.Func2[Microsoft.OData.Client.DataServiceCollection`1[Api.Models.UserRoleSubProduct],System.Boolean]])'

I think I am close enough. Any help is appreciated


Solution

  • Ignoring the redundant unused lambda expressions, the problem is in the last 2 lines.

    First, you are using a wrong generic type (MakeGenericMethod(property1.Type)), while the correct type is basically the type of the parameter x here

    .Any(x => x.SubProductID == lead.SubProductID)
    

    =>

    .Any<T>((T x) => ...)
    

    which maps to param3.Type in your code.

    Second, the second argument of the Any must be lambda expression (not simply equality as in the code).

    Third, since user.UserRoleSubProducts most likely is a collection type, you should emit call to Enumerable.Any rather than Queryable.Any.

    The Expression.Call method has overload which is very handy for "calling" static generic extension methods:

    public static MethodCallExpression Call(
        Type type,
        string methodName,
        Type[] typeArguments,
        params Expression[] arguments
    )
    

    So the last two lines can be replaced with:

    var anyCall = Expression.Call(
        typeof(Enumerable), nameof(Enumerable.Any), new Type[] { param3.Type },
        property1, Expression.Lambda(equality, param3)
    );