So, we're going to need several helper methods here, but once we have them, things should be fairly simple.
We'll start out with this class that can replace all instance of one expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Then an extension method to make calling it easier:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Next we'll write a compose extension method. This will take a lambda that computes an intermediate result, and then another lambda that computes a final result based on the intermediate result and returns a new lambda that takes what the initial lambda returns and returns the output of the final lambda. In effect it calls one function, and then calls another on the result of the first, but with expressions, not methods.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Now we'll create a Combine
method. This will be similar, but subtly different. It will take a lambda that computes an intermediate result, and a function that uses both the initial input, and the intermediate input, to compute a final result. It's basically the same as the Compose
method, but the second function also gets to know about the first parameter:
public static Expression<Func<TFirstParam, TResult>>
Combine<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], param)
.Replace(second.Parameters[1], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Okay, now that we have all of this, we get to use it. First thing we'll do is create a static constructor; we won't be able to inline all we have to do into the field initializer. (The other option is to make a static method that computes this, and have the initializer call it.)
After that we'll create an expression that take a person and returns it's address. It's one of the missing puzzle pieces in the expressions that you have. Using that, we'll compose that address selector with the AddressDto
selector, and then use Combine
on that. Using that we have a lambda that takes a Person
and an AddressDTO
and returns a PersonDTO
. So in there we have basically what you wanted to have, but with an address
parameter given to us to assign to the address:
static PersonDto()
{
Expression<Func<Person, Address>> addressSelector =
person => person.Address;
Selector = addressSelector.Compose(AddressDto.Selector)
.Combine((entity, address) => new PersonDto
{
Id = entity.Id,
Name = entity.Name,
Address = address,
});
}