HamedFathi
HamedFathi

Reputation: 3979

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?

Upvotes: 0

Views: 289

Answers (1)

xanatos
xanatos

Reputation: 111870

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.

Upvotes: 1

Related Questions