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
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With