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.
- How do I set the value of a property using expressions?
- How is the value of the property selector Expression <Func <T, TResult ->> set
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
source to share
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);
}
source to share