The question is very similar to Dynamically generate LINQ select with nested properties
public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(string members) =>
BuildSelector<TSource, TTarget>(members.Split(',').Select(m => m.Trim()));
public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(IEnumerable<string> members)
{
var parameter = Expression.Parameter(typeof(TSource), "e");
var body = NewObject(typeof(TTarget), parameter, members.Select(m => m.Split('.')));
return Expression.Lambda<Func<TSource, TTarget>>(body, parameter);
}
static Expression NewObject(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var bindings = new List<MemberBinding>();
var target = Expression.Constant(null, targetType);
foreach (var memberGroup in memberPaths.GroupBy(path => path[depth]))
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length);
var targetValue = !childMembers.Any() ? sourceMember :
NewObject(targetMember.Type, sourceMember, childMembers, depth + 1);
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
return Expression.MemberInit(Expression.New(targetType), bindings);
}
The nice generic solution provided by Ivan Stoev works fine but the problem is that it doesn't support collection properties.
For example, source.Property1.Property2 - if Property1 is collection of users than the code doesn't work as Property2 is not the property of the collection but of the Property1 type.
class Shipment {
// other fields...
public int Id;
public Address Sender;
public List<Address> Recipients;
}
class Address {
public string AddressText;
public string CityName;
public string CityId;
}
With the classes and code above I can query
var test = BuildSelector<Shipment, Shipment>(
"Id, Sender.CityId, Sender.CityName");
But if I want to get only city names for recipients I cannot do the following (because recipients is collection) :
var test = BuildSelector<Shipment, Shipment>(
"Recipients.CityName");
I am new to C# expressions and cannot figure out how to improve the above mentioned solution to make it work with collection properties.