Daniel Daniel - 16 days ago 5
C# Question

Convert Linq expression "obj => obj.Prop" into "parent => parent.obj.Prop"

I have an existing expression of type

Expression<Func<T, object>>
; it contains values like
cust => cust.Name
.

I also have a parent class with a field of type
T
. I need a method that accepts the above as a parameter and generates a new expression that takes the parent class (
TModel
) as a parameter.

Thus,
cust => cust.Name
becomes
parent => parent.Customer.Name
.

Likewise,
cust => cust.Address.State
becomes
parent => parent.Customer.Address.State
.

Here's my initial version:

//note: the FieldDefinition object contains the first expression
//described above, plus the MemberInfo object for the property/field
//in question
public Expression<Func<TModel, object>> ExpressionFromField<TModel>(FieldDefinition<T> field)
where TModel: BaseModel<T>
{
var param = Expression.Parameter(typeof(TModel), "t");

//Note in the next line "nameof(SelectedItem)". This is a reference
//to the property in TModel that contains the instance from which
//to retrieve the value. It is unqualified because this method
//resides within TModel.
var body = Expression.PropertyOrField(param, nameof(SelectedItem));
var member = Expression.MakeMemberAccess(body, field.Member);
return Expression.Lambda<Func<TModel, object>>(member, param);
}


The error I'm currently receiving is when I have a field with multiple parts (i.e.
cust.Address.State
instead of just
cust.Name
). I get an error on the
var member
line that the specified member doesn't exist--which is true, since the body at that refers to the parent's child (
Customer
) and not the item that contains the member (
Address
).

Here's what I wish I could do:

public Expression<Func<TModel, object>> ExpressionFromField<TModel>(FieldDefinition<T> field)
where TModel: BaseModel<T>
{
var param = Expression.Parameter(typeof(TModel), "t");
var body = Expression.PropertyOrField(param, nameof(SelectedItem));
var IWantThis = Expression.ApplyExpressionToField(field.Expression, body);
return Expression.Lambda<Func<TModel, object>>(IWantThis, param);
}


Any help getting to this point would be greatly appreciated.

Answer

What you're looking for is the ability to compose expressions, just as you can compose functions:

public static Expression<Func<T, TResult>> Compose<T, TIntermediate, TResult>(
    this Expression<Func<T, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    return Expression.Lambda<Func<T, TResult>>(
        second.Body.Replace(second.Parameters[0], first.Body),
        first.Parameters[0]);
}

This relies on the following method to replace all instances of one expression with another:

public 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 ex)
    {
        if(ex == from) return to;
        else return base.Visit(ex);
    }  
}

public static Expression Replace(this Expression ex,
    Expression from,
    Expression to)
{
    return new ReplaceVisitor(from, to).Visit(ex);
}

You can now take an expression selecting a property:

Expression<Func<Customer, object>> propertySelector = cust => cust.Name;

And an expression selecting that object from the model:

Expression<Func<CustomerModel, Customer>> modelSelector = model => model.Customer;

and compose them:

Expression<Func<Customer, object> magic = modelSelector.Compose(propertySelector);
Comments