Search code examples
c#expression-trees

How to write an expression tree for selecting inside of SelectMany?


Consider the following Person class

    // Person.cs
    public class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string FamilyName { get; set; }
        public float Age { get; set; }
        public DateTimeOffset BithDate { get; set; }
        public IEnumerable<Address> Addresses { get; set; }
        public Person()
        {
            Addresses = new List<Address>();
        }
    }

    // Address.cs
    public class Address
    {
        public string Country { get; set; }
        public string City { get; set; }
        public string MainStreet { get; set; }
        public string Info { get; set; }
        public string No { get; set; }
    }

Now, I have the snippet code below

var list = PeopleDataGenerator.GetPeople()
    .SelectMany(x => x.Addresses)
    .Select(x => x.City)
    ;

How can I do this part .SelectMany(x => x.Addresses) .Select(x => x.City) with Expression Tree?


Solution

  • Unclear if you want it for IEnumerable or IQueryable... For IEnumerable:

    Given:

    /// <summary>
    /// IEnumerable&lt;TResult&gt; Enumerable.SelectMany&lt;TSource, TResult&gt;(IEnumerable&lt;TSource&gt; source, Func&lt;TSource, IEnumerable&lt;TResult&gt;&gt; selector)
    /// </summary>
    public static readonly MethodInfo SelectMany1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                        where x.Name == nameof(Enumerable.SelectMany)
                                                        let args = x.GetGenericArguments()
                                                        where args.Length == 2
                                                        let pars = x.GetParameters()
                                                        where pars.Length == 2 &&
                                                            pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                            pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], typeof(IEnumerable<>).MakeGenericType(args[1]))
                                                        select x).Single();
    
    /// <summary>
    /// IEnumerable&lt;TResult&gt; Enumerable.Select&lt;TSource, TResult&gt;(IEnumerable&lt;TSource&gt; source, Func&lt;TSource, TResult&gt; selector)
    /// </summary>
    public static readonly MethodInfo Select1 = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                    where x.Name == nameof(Enumerable.Select)
                                                    let args = x.GetGenericArguments()
                                                    where args.Length == 2
                                                    let pars = x.GetParameters()
                                                    where pars.Length == 2 &&
                                                        pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                        pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                                    select x).Single();
    

    (I have a gist full of these definitions)

    You can

    // SelectMany
    var par1 = Expression.Parameter(typeof(Person));
    var sub1 = Expression.Property(par1, nameof(Person.Addresses));
    var lambda1 = Expression.Lambda<Func<Person, IEnumerable<Address>>>(sub1, par1);
    var selectMany = Expression.Call(SelectMany1.MakeGenericMethod(typeof(Person), typeof(Address)), par0, lambda1);
    
    // Select
    var par2 = Expression.Parameter(typeof(Address));
    var sub2 = Expression.Property(par2, nameof(Address.City));
    var lambda2 = Expression.Lambda<Func<Address, string>>(sub2, par2);
    var select = Expression.Call(Select1.MakeGenericMethod(typeof(Address), typeof(string)), selectMany, lambda2);
    
    // persons => Select(SelectMany(persons))
    var par0 = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeof(Person)));
    var lambda0 = Expression.Lambda<Func<IEnumerable<Person>, IEnumerable<string>>>(select, par0);
    var compiled = lambda0.Compile();
    

    If you want the SelectMany+Select call it is in the select variable. If you want a compilable expression that uses the SelectMany+Select, it is in lambda0 and in compiled.