I Can’t seem to find the answer to this anywhere…
The objective is to find a way to do linq .join() using Expression tree statements.
So… using Microsoft example:
class Person
{
public string Name { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public static void JoinEx1()
{
Person magnus = new Person { Name = "Hedlund, Magnus" };
Person terry = new Person { Name = "Adams, Terry" };
Person charlotte = new Person { Name = "Weiss, Charlotte" };
Pet barley = new Pet { Name = "Barley", Owner = terry };
Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
List<Person> people = new List<Person> { magnus, terry, charlotte };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy };
// Join the list of Person objects and the list of Pet objects
// to create a list of person-pet pairs where each element is
// an anonymous type that contains the name of pet and the name
// of the person that owns the pet.
var query = people.AsQueryable().Join(pets,
person => person,
pet => pet.Owner,
(person, pet) =>
new { OwnerName = person.Name, Pet = pet.Name });
Can anyone help me on how to do the .join() using Expression trees?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Since Queryable.Join is a generic static method, it is easiest to use an extension method to lookup the MethodInfo for the one you need:
public static class TypeExt {
public static MethodInfo GetMethod(this Type t, string methodName, int paramCount) =>
t.GetMethods().Where(mi => mi.Name == methodName && mi.GetParameters().Length == paramCount).Single();
}
The Join method takes five parameters (extension methods pass the object they are applied to as the first parameter), so we will build them as Expressions one at a time. The five parameters are the IQueryable<>, the IEnumerable<> to join, the outer key selector lambda, the inner key selector lambda and the result selector lambda.
// Build Queryable.Join<TOuter,TInner,TKey,TResult> and use as query expression
// IQueryable<TOuter>
var arg0 = Expression.Constant(people.AsQueryable());
// IEnumerable<TInner>
var arg1 = Expression.Constant(pets);
// TOuter person
var arg2p = Expression.Parameter(people.GetType().GetGenericArguments()[0], "person");
// also TKey person
// Expression<Func<TOuter,TKey>>: person => person
var arg2 = Expression.Quote(Expression.Lambda(arg2p, arg2p));
// TInner pet
var arg3p = Expression.Parameter(pets.GetType().GetGenericArguments()[0], "pet");
// TKey pet.Owner
var arg3body = Expression.Property(arg3p, "Owner");
// Expression<Func<TInner,TKey>>: pet => pet.Owner
var arg3 = Expression.Quote(Expression.Lambda(arg3body, arg3p));
// TResult = typeof(new { string OwnerName , string Pet })
var anonymousType = (new { OwnerName = default(string), Pet = default(string) }).GetType();
// .ctor
var arg4Constructor = anonymousType.GetConstructors()[0];
// person.Name
var arg4PersonName = Expression.Property(arg2p, "Name");
// pet.Name
var arg4PetName = Expression.Property(arg3p, "Name");
var arg4Args = new[] { arg4PersonName, arg4PetName };
// new[] { .OwnerName, .Pet }
var arg4Members = anonymousType.GetProperties();
// new { OwnerName = person.Name, Pet = pet.Name }
var arg4body = Expression.New(arg4Constructor, arg4Args, arg4Members);
// Expression<Func<TOuter,TInner,TResult>>: (person,pet) => new { OwnerName = person.Name, Pet = pet.Name }
var arg4 = Expression.Quote(Expression.Lambda(arg4body, arg2p, arg3p));
Note: For complicated nested lambda closure reasons, each lambda is surrounded by Expression.Quote so the Expression compiler will know to return an Expression tree and not a delegate. In this example it makes no difference.
Now using the extension method you can lookup the Join you need to use, instantiate it from a generic method to the specific method for the types you are querying, and create the Join method call expression:
var joinGenericMI = typeof(Queryable).GetMethod("Join", 5);
var joinMI = joinGenericMI.MakeGenericMethod(new[] { arg2p.Type, arg3p.Type, arg2p.Type, anonymousType });
var qExpr = Expression.Call(joinMI, arg0, arg1, arg2, arg3, arg4);
Finally, you can create an IQueryable<> with the Expression:
var q2 = people.AsQueryable().Provider.CreateQuery(qExpr);
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0