Search code examples
c#linqexpression-trees

How to select Item from an property list using Expression Tree in c#


I have a lambda expression as follows:

var source = new List<Entidade>();

var z = source.Select<Entidade, Resultado>(
                s =>
                new Resultado
                    {
                        Detalhes =
                            new List<DetalheResultado>(
                            s.Detalhes.Select<Detalhe, DetalheResultado>(
                                t => new DetalheResultado { Id = t.Id, Valor = t.Valor }))
                    });

I am trying to execute the same query with Expressions with the following code:

ParameterExpression sourceItem = Expression.Parameter(typeof(Entidade), "s");

var source3 = Expression.Parameter(typeof(Detalhe), "t");
var property3 = typeof(DetalheResultado).GetProperty("Id");
var member3 = Expression.Property(source3, "Id");
var itemResult3 = Expression.New(typeof(DetalheResultado).GetConstructor(Type.EmptyTypes));
var memberBinding3 = Expression.Bind(property3, member3);
var memberInit3 = Expression.MemberInit(itemResult3, memberBinding3);
var selector3 = Expression.Lambda(memberInit3, source3);

var detalhes = Expression.Property(sourceItem, "Detalhes");

// here you get an error
var lista3 = Expression.Call(
    typeof(Queryable), 
    "Select", 
    new Type[] { typeof(Detalhe), typeof(DetalheResultado) },
    detalhes, 
    selector3);

var listaResultado = typeof(DetalheResultado).GetProperty("Detalhes");
var memberBindigs4 = Expression.Bind(listaResultado, lista3);

...

but running this code I got the error:

No generic method 'Select ' on ' System.Linq.Queryable ' type is compatible with the arguments and the supplied type arguments. Any argument must be provided if the method is not generic.

I consulted the DebugView expression and implemented expressions as its return, but get the aforementioned error.

Any suggestions?


Solution

  • I have never had luck with using that Expression.Call method on the LINQ generic methods. I always fetch it separately (see variables firstSelectMethod and secondSelectMethod). I don't know why, and if someone else knows why that won't work, I would be much obliged. The below code works, though I made some assumptions about what your classes look like.

    Please note that I substituted Queryable for Enumerable.

    var paramS = Expression.Parameter(typeof(Entidade), "s");
    var paramT = Expression.Parameter(typeof(Detalhe), "t");
    
    var firstSelectMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Select").MakeGenericMethod(typeof(Entidade), typeof(Resultado));
    var secondSelectMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Select").MakeGenericMethod(typeof(Detalhe), typeof(DetalheResultado));
    
    var lista4 = Expression.Call(
        firstSelectMethod,
        Expression.Constant(source),
        Expression.Lambda(
            Expression.MemberInit(
                Expression.New(typeof(Resultado).GetConstructor(Type.EmptyTypes)), 
                Expression.Bind(
                    typeof(Resultado).GetProperty("Detalhes"), 
                    Expression.New(
                        typeof(List<DetalheResultado>).GetConstructor(new Type[] {typeof(IEnumerable<DetalheResultado>)}),
                        Expression.Call(
                            secondSelectMethod,
                            Expression.Property(
                                paramS,
                                "Detalhes"
                            ),
                            Expression.Lambda(
                                Expression.MemberInit(
                                    Expression.New(typeof(DetalheResultado).GetConstructor(Type.EmptyTypes)), 
                                    Expression.Bind(
                                        typeof(DetalheResultado).GetProperty("Id"),
                                        Expression.Property(paramT, "Id")
                                    ),
                                    Expression.Bind(
                                        typeof(DetalheResultado).GetProperty("Valor"),
                                        Expression.Property(paramT, "Valor")
                                    )
                                ),
                                paramT
                            )
                        )
                    )
                )
            ), 
            paramS
        )
    );