How to combine several similar SELECT-expressions into a single expression?
private static Expression> CombineSelector
In case anyone else stumbles upon this with a similar use case as mine (my selects targeted different classes based on the level of detail needed):
Simplified scenario:
public class BlogSummaryViewModel
{
public string Name { get; set; }
public static Expression> Map()
{
return (i => new BlogSummaryViewModel
{
Name = i.Name
});
}
}
public class BlogViewModel : BlogSummaryViewModel
{
public int PostCount { get; set; }
public static Expression> Map()
{
return (i => new BlogViewModel
{
Name = i.Name,
PostCount = i.Posts.Count()
});
}
}
I adapted the solution provided by @Marc Gravell like so:
public static class ExpressionMapExtensions
{
public static Expression> Concat(
this Expression> mapA, Expression> mapB)
where TTargetB : TTargetA
{
var param = Expression.Parameter(typeof(TSource), "i");
return Expression.Lambda>(
Expression.MemberInit(
((MemberInitExpression)mapB.Body).NewExpression,
(new LambdaExpression[] { mapA, mapB }).SelectMany(e =>
{
var bindings = ((MemberInitExpression)e.Body).Bindings.OfType();
return bindings.Select(b =>
{
var paramReplacedExp = new ParameterReplaceVisitor(e.Parameters[0], param).VisitAndConvert(b.Expression, "Combine");
return Expression.Bind(b.Member, paramReplacedExp);
});
})),
param);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression original;
private readonly ParameterExpression updated;
public ParameterReplaceVisitor(ParameterExpression original, ParameterExpression updated)
{
this.original = original;
this.updated = updated;
}
protected override Expression VisitParameter(ParameterExpression node) => node == original ? updated : base.VisitParameter(node);
}
}
The Map method of the extended class then becomes:
public static Expression> Map()
{
return BlogSummaryViewModel.Map().Concat(i => new BlogViewModel
{
PostCount = i.Posts.Count()
});
}