How do I set a value from an expression for nested depth levels?

My question is very similar to the next two questions, but I have an additional requirement that they do not satisfy.

Like those questions, I have Expression<Func<TEntity, TProperty>>

where I want to set a value for a specified property. And these solutions work great if the body of the expression is only one level, for example x => x.FirstName

, but they don't work at all if that body is deeper, for example x => x.Parent.FirstName

.

Is there a way to take this deeper expression and set a value for it? I don't need a very robust deferred solution, but I do need something that I can execute on an object and this will work, whether at level 1 or multiple levels. I also need to support the most common types that you would expect in a database ( long

, int?

, string

, Decimal

, DateTime?

, etc. Although I do not need more complex things, such as geo types).

For the sake of conversation, let's say we're working with these objects, although let's say we need to process 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 are looking at (I am not in the mood for it to be an extension method, but it looks 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
}

      

PS This version of the code was written in an SO editor - my apologies for the dumb syntax problems, but that should be pretty darn close! #LockedDownWorkstationsSuck

+3


source to share


1 answer


As I said in the comments, it shouldn't be difficult. With a selector, just add the assignment to the expression. You 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);
}

      

+4


source







All Articles