11

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. This will be used as an expression parameter of an MVC method.

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.

Edit: This was marked as a possible duplicate of this question; however, the only real similarity is the solution (which is, in fact, the same). Composing expressions is not an intuitive solution to accessing nested properties via expressions (unless one's inuition is guided by certain experience, which should not be assumed). I also edited the question to note that the solution needs to be suitable for a paramter of an MVC method, which limits the possible solutions.

7
  • Why is it not an option to call these expressions from the children properties of the parent instead of making them mutate to work directly on the parent? Commented Jun 2, 2016 at 21:56
  • Using parent => (cust => cust.Name)(parent.Customer) would be much simpler. Commented Jun 2, 2016 at 22:06
  • @TravisJ, I'm using all this to wire up base classes, so few of the specifics are known upfront. There may be a better way to do what I'm doing, but this way will work. Commented Jun 2, 2016 at 22:29
  • @poke, I'm not sure what that does. Are you casting a customer as an expression? And can you do the same thing with unknown expressions (I don't know the type of parent, cust, or the field or type of Name upfront. Commented Jun 2, 2016 at 22:30
  • 1
    Possible duplicate of How do I compose Linq Expressions? ie Func<Exp<Func<X, Y>>, Exp<Func<Y, Z>>, Exp<Func<X, Z>>> Commented Dec 13, 2016 at 14:58

1 Answer 1

16

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);
Sign up to request clarification or add additional context in comments.

2 Comments

Holy crap that is SO COOL. I was able to quickly implement this, but I'll want to spend some more time with it to figure out exactly how it works. I also made a couple quick edits to your answer; Intermediate was misspelled once and once of the parameter types was missing.
Youll want to return to in your visit method as well

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.