Need help converting "Any()" lambda to an expression tree
Asked Answered
S

1

0

Consider the following. (which works, but I need Accounts to be passed in as a string. Accounts is list of Account)

repo = repo.Where(x => x.Accounts.Any(p => p.Id == 1));

Here is what I have so far, but I can't seem to wire them together at the end.

        var parameterExp = Expression.Parameter(typeof(Car), "x");
        Expression propertyExp = Expression.Property(parameterExp, "Accounts");

        //Type elementType = propertyExp.Type.GetGenericArguments()[0];

        MethodInfo AnyMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static).First(m => m.Name == "Any");

        var parameterExp2 = Expression.Parameter(typeof(Car), "p");

        var idExpr = Expression.PropertyOrField(parameterExp2, "Id");

        MethodInfo method = typeof(long).GetMethod("Equals", new[] { typeof(long) });
        var _relatedToId = Expression.Constant(relatedToId, typeof(long));
        var equalsMethodExp = Expression.Call(idExpr, method, _relatedToId);

        // This is where it breaks. I can't seem to wire it together correctly.
        var call = Expression.Call(
            AnyMethod,
            propertyExp,
            equalsMethodExp);

        Expression<Func<Car, bool>> predicate = Expression.Lambda<Func<Car, bool>>(call, parameterExp);

        // Need to return x => x.Accounts.Any(p => p.Id.Equals(1))
        return predicate;

Thanks in advance

Staton answered 24/9, 2017 at 17:8 Comment(2)
Please explain what is type of x and pCollogue
x is of type Car and p is of type Account where "Accounts" is a list of Account. i.e. MyCar.AccountsStaton
C
0

You have a several mistakes:

  • Firstly, uncomment line below

    Type elementType = propertyExp.Type.GetGenericArguments()[0];
    
  • Secondly, you don't correctly retrieve MethodInfo for Any. You need info that contains two parameters (source, func) and you need to get the closed type of Any Any<Account> not the open Any<>:

    MethodInfo AnyMethod = typeof(Enumerable)
                           .GetMethods(BindingFlags.Public | BindingFlags.Static)
                           .First(m => m.Name == "Any" && m.GetParameters().Length > 1);
    var closedAnyMethod = AnyMethod.MakeGenericMethod(elementType);
    
  • Third, you have incorrect type for parameterExp2. It should be Account not Car:

    var parameterExp2 = Expression.Parameter(elementType, "p");
    
  • Fourthly, you should convert a MethodCallExpression for Equals method to lambda expression. You cannot directly pass Equals' call expression to Any because Any gets Func<T, bool> as parameter:

    var equalsMethodExp = Expression.Call(idExpr, method, _relatedToId);
    var lambdaAny = Expression.Lambda(typeof(Func<,>).MakeGenericType(elementType, typeof(bool)), equalsMethodExp, parameterExp2);
    
  • Finally, you just need to call another override of Expression.Call:

    var call = Expression.Call(
            closedAnyMethod,
            propertyExp,
            lambdaAny);
    

Let me know if you want to get the complete code.

Collogue answered 24/9, 2017 at 17:55 Comment(5)
This is really close. Accounts is being passed in as a string. Car is the only known type at run time. I've been getting the Account type from Type elementType = propertyExp.Type.GetGenericArguments()[0]; This gives Type Account. Everything works up until the lambdaAny var lambdaAny = Expression.Lambda<Func<Account, bool>>(equalsMethodExp, parameterExp2); typeof(elementType) doesn't seem to satisfy Func.Staton
Can you give more info what's wrong with var lambdaAny = Expression.Lambda<Func<Account, bool>>(equalsMethodExp, parameterExp2);? It's compilte or runtime error or it's just doesn't work correct? Can you post how is represented equalsMethodExp in the debug? Does It seem like this: {p.Id.Equals(1)}?Collogue
Account in your example is a concrete type as you have it. I don't now what Account is going to be at compile time. The type will change. I need something like Expression.Lambda<Func<UnknownType, bool>>(equalsMethodExp, parameterExp2); Unless I have a concrete type in Func, it won't build. equalsMethodExp has the correct valueStaton
@Staton Please check answer, I edited it. Now type is retrieved in the runtime from propertyExp. If you want you can pass it from somewhere.Collogue
Works!! Thank you so much George!Staton

© 2022 - 2024 — McMap. All rights reserved.