6

My question is very similar to the following two questions but I have an added requirement that these do not satisfy.

Just like those questions, I have an Expression<Func<TEntity, TProperty>> where I want to set a value to the specified property. And those solutions work great if the body of the expression is only one level deep, such as x => x.FirstName but they don't work at all if that body is deeper, like x => x.Parent.FirstName.

Is there some way to take this deeper expression and set the value to it? I don't need a terribly robust execution-deferred solution but I do need something that I can execute on an object and it would work whether 1-level or multiple levels deep. I also need to support most typical types you'd expect in a database (long, int?, string, Decimal, DateTime?, etc. although I don't care about more complex things like geo types).

For conversation's sake, let's say we're working with these objects although assume we need to handle N levels deep, not just 1 or 2:

public class Parent
{
    public string FirstName { get; set; }
}

public class Child
{
    public Child()
    {
        Mom = new Parent(); // so we don't have to worry about nulls
    }

    public string FavoriteToy { get; set; }
    public Parent Mom { get; set; }
}

and let's say this is our unit test:

[TestFixture]
public class Tests
{
    [Test]
    public void MyTest()
    {
        var kid = new Child();
        Expression<Func<Child, string>> momNameSelector = (ch => ch.Mom.FirstName);
        Expression<Func<Child, string>> toyNameSelector = (ch => ch.FavoriteToy);

        kid.ExecuteMagicSetter(momNameSelector, "Jane");
        kid.ExecuteMagicSetter(toyNameSelector, "Bopp-It!");

        Assert.That(kid.Mom.FirstName, Is.EqualTo("Jane"));
        Assert.That(kid.FavoriteToy, Is.EqualTo("Bopp-It!"));
    }
}

and our extension method we're looking at (I'm not set on it needing to be an extension method but it seems simple enough) would look like this:

public static TEntity ExecuteMagicSetter<TEntity, TProperty>(this TEntity obj, Expression<Func<TEntity, TProperty>> selector, TProperty value)
    where TEntity : class, new() // I don't require this but I can allow this restriction if it helps
{
    // magic
}

P.S. This version of code was written in the SO editor - my apologies for dumb syntax issues but this should be darn close! #LockedDownWorkstationsSuck

Community
  • 1
  • 1
Jaxidian
  • 13,081
  • 8
  • 83
  • 125
  • It shouldn't matter how deeply nested your member expressions are, just as long as the expression ends up at a property/field. Then with that expression, you can add an assignment and you're done. – Jeff Mercado Dec 22 '14 at 17:03
  • @JeffMercado Every solution from those questions I've attempted ends up having the problem of using reflection to call a setter method on (in my example above) the `Parent` object but with a passed in instance of the `Child` object, so there's a type mismatch at runtime that fails. I feel the missing magic is either recursively or iteratively getting child property objects and then passing those into the setter method. – Jaxidian Dec 22 '14 at 17:07
  • Well include your attempts at the problem then. The problem you're describing doesn't sound like one related to the nested expressions. If you're not generating those selector expressions by hand and letting the compiler handle it, it will have to be a valid expression in the first place. – Jeff Mercado Dec 22 '14 at 17:09
  • My attempted solutions are those posted in the questions I listed as being very similar to my question. I can reproduce those here if you wish but I felt that was redundant. Is that what you would like? – Jaxidian Dec 22 '14 at 17:11
  • Duplicate of http://stackoverflow.com/questions/10939750/c-how-to-set-properties-and-nested-propertys-properties-with-expression – Steve Lillis Dec 22 '14 at 17:14
  • @SteveLillis That looks promising. I'll confirm very shortly if that's the same or not. Thanks!! – Jaxidian Dec 22 '14 at 17:17
  • Oh I see the problem now. The other questions tried to mix in reflection to do the assignment. That's not necessary when you can simply create the full assignment expression and run it. – Jeff Mercado Dec 22 '14 at 17:29
  • Both of these work. I would call this question a duplicate of the one @SteveLillis linked too but also, Jeff Mercado's solution below is a better solution, imho. Thanks everybody!! – Jaxidian Dec 22 '14 at 17:58

1 Answers1

9

As I stated in the comments, it shouldn't be all that complicated. With the selector, just add an assignment to the expression. You'll just need to compile and run the expression.

public static TEntity ExecuteMagicSetter<TEntity, TProperty>(
        this TEntity obj,
        Expression<Func<TEntity, TProperty>> selector,
        TProperty value)
{
    var setterExpr = CreateSetter(selector);
    setterExpr.Compile()(obj, value);
    return obj;
}

private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>
        (Expression<Func<TEntity, TProperty>> selector)
{
    var valueParam = Expression.Parameter(typeof(TProperty));
    var body = Expression.Assign(selector.Body, valueParam);
    return Expression.Lambda<Action<TEntity, TProperty>>(body,
        selector.Parameters.Single(),
        valueParam);
}
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272