Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set a value from an Expression for nested levels of depthness?

Tags:

c#

.net-4.0

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

  • How to set property value using Expressions?
  • How set value a property selector Expression<Func<T,TResult>>

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

like image 334
Jaxidian Avatar asked Dec 22 '14 16:12

Jaxidian


Video Answer


1 Answers

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);
}
like image 180
Jeff Mercado Avatar answered Sep 28 '22 19:09

Jeff Mercado