33

i need associate a entity property Address in my Person class entity with expressions linq in my FactoryEntities class using pattern factory idea, look this is what I have and I want to do:

Address address = new Address();
address.Country = "Chile";
address.City = "Santiago";
address.ZipCode = "43532";
//Factory instance creation object
//This is idea
Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address);

public class Person: Entity
{
    public string Name{ get; set; }
    public string LastName{ get; set; }
    public Address Address{ get; set; }
}

public class Address: Entity
{
    public string Country{ get; set; }
    public string City{ get; set; }
    public string ZipCode{ get; set; }
}

public class FactoryEntity<TEntity> where TEntity : Entity
{
    public void AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> entityExpression, TProperty newValueEntity) where TProperty : Entity
    {
        if (instanceEntity == null || instanceEntity.IsTransient())
            throw new ArgumentNullException();

        /*TODO: Logic the association and validation 
        How set the newValueEntity into the property of entityExpression (x=>x.Direccion = direccion*/
    }
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Emilio Montes
  • 355
  • 1
  • 3
  • 6
  • The `propertyInfo` you have there is for a `TEntity`, not a `TProperty`. You cannot use it to access a property for an object of a different type. Which way is the association supposed to go? What you're trying to do here doesn't make sense to me. – Jeff Mercado Nov 12 '11 at 20:12
  • It's very buggy code, and is not easy say what you want please clarify it. – Saeed Amiri Nov 12 '11 at 20:15
  • I'm sorry the explanation was not clear, but i want to do is to occupy a factory that allows me to relate objects to intercepting validate the association so it is properly check – Emilio Montes Nov 13 '11 at 01:02
  • possible duplicate of [Property selector Expression>. How to get/set value to selected property](http://stackoverflow.com/questions/5075484/property-selector-expressionfunct-how-to-get-set-value-to-selected-property) – nawfal Apr 17 '13 at 20:44

7 Answers7

34

This works:

The following helper method converts a getter expression into a setter delegate. If you want to return an Expression<Action<T,TProperty>> instead of an Action<T,TProperty>, just don't call the Compile() method at the end.

Note: The code is from Ian Mercer's blog: http://blog.abodit.com/2011/09/convert-a-property-getter-to-a-setter/

    /// <summary>
    /// Convert a lambda expression for a getter into a setter
    /// </summary>
    public static Action<T, TProperty> GetSetter<T, TProperty>(Expression<Func<T, TProperty>> expression)
    {
        var memberExpression = (MemberExpression)expression.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var setMethod = property.GetSetMethod();

        var parameterT = Expression.Parameter(typeof(T), "x");
        var parameterTProperty = Expression.Parameter(typeof(TProperty), "y");

        var newExpression =
            Expression.Lambda<Action<T, TProperty>>(
                Expression.Call(parameterT, setMethod, parameterTProperty),
                parameterT,
                parameterTProperty
            );

        return newExpression.Compile();
    }
Japple
  • 965
  • 7
  • 14
smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • How do you call this method? I can't seem to get the correct arguments into the delegate. – h bob Aug 30 '15 at 09:45
  • 2
    @hbob , something like: `GetSetter( (string example) => example.Length )` – smartcaveman Aug 31 '15 at 12:03
  • 1
    How about if you don't know TProperty? Using Expression.Lambda> does not work :( – markmnl Sep 10 '16 at 07:43
  • @markmnl http://stackoverflow.com/questions/729295/how-to-cast-expressionfunct-datetime-to-expressionfunct-object might help – Dave Cousineau Sep 16 '16 at 17:46
  • @markmnl I have the same problem, did you find a solution for this? – S. Robijns Jan 31 '17 at 13:44
  • @S.Robijns @markmnl If you want to use an object parameter in the lambda expression, but the signature expects something more specific, then you'll need to cast the object-typed parameter expression to the specific type you need for the call expression to be legal. The factory method you'll need is [`Expression.Convert`](https://msdn.microsoft.com/en-us/library/bb292051(v=vs.110).aspx). – smartcaveman Jan 31 '17 at 16:29
  • @smartcaveman but If you don't know what the specific type is it's not possible? – S. Robijns Feb 01 '17 at 11:53
  • While this probably works, aren't you creating a lambda that calls a `PropertyInfo` setter (Which I guess is a `MethodInfo`)? So the lambda that you construct is unnecessarily using reflection, rather than being an actual property assignment expression. – Dave Cousineau May 29 '18 at 18:17
  • 1
    @DaveCousineau maybe, have you checked the IL for this answer and for yours? I do know that this answer is pretty old, and that `Expression.Assign` came along in .NET 4.0 whereas @IanMercer's code is compatible with .NET 3.5 . I'm not sure if there's a difference in what actually gets executed though - we'd need to test it – smartcaveman May 31 '18 at 15:59
  • @smartcaveman I think you might be right. I was thinking this was something like `Expression.Call` of `MethodInfo.Invoke`, but no, it's using the `MethodInfo` only to identify the method to call, similar to how my answer uses a `PropertyInfo` to identify a property. – Dave Cousineau May 31 '18 at 17:33
7

You can set the property like this:

public void AssociateWithEntity<TProperty>(
    Expression<Func<TEntity, TProperty>> entityExpression,
    TProperty newValueEntity)
    where TProperty : Entity
{
    if (instanceEntity == null)
        throw new ArgumentNullException();

    var memberExpression = (MemberExpression)entityExpression.Body;
    var property = (PropertyInfo)memberExpression.Member;

    property.SetValue(instanceEntity, newValueEntity, null);
}

This will work only for properties, not fields, although adding support for fields should be easy.

But the code you have for getting the person won't work. If you want to keep the void return type of AssociateWithEntity(), you could do it like this:

var factory = new FactoryEntity<Person>();
factory.AssociateWithEntity(p => p.Address, address);
Person person = factory.InstanceEntity;

Another option is a fluent interface:

Person person = new FactoryEntity<Person>()
    .AssociateWithEntity(p => p.Address, address)
    .InstanceEntity;
svick
  • 236,525
  • 50
  • 385
  • 514
5

Another solution is to get the property owner and invoke the property setter using reflection. The advantage of this solution is that it does not use extension methods and can be called with any type.

private void SetPropertyValue(Expression<Func<object, object>> lambda, object value)
{
  var memberExpression = (MemberExpression)lambda.Body;
  var propertyInfo = (PropertyInfo)memberExpression.Member;
  var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
  var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();    
  propertyInfo.SetValue(propertyOwner, value, null);            
}
...
SetPropertyValue(s => myStuff.MyPropy, newValue);
Kols
  • 3,641
  • 2
  • 34
  • 42
Rytis I
  • 1,105
  • 8
  • 19
  • 1
    `lambda.Body` may be `UnaryExpression`, see http://stackoverflow.com/questions/12420466/unable-to-cast-object-of-type-system-linq-expressions-unaryexpression-to-type – xmedeko Apr 23 '15 at 07:44
1

This is my solution that uses Expression.Assign, but after looking more closely, the accepted answer is just as good.

// optionally or additionally put in a class<T> to capture the object type once
// and then you don't have to repeat it if you have a lot of properties
public Action<T, TProperty> GetSetter<T, TProperty>(
   Expression<Func<T, TProperty>> pExpression
) {
   var parameter1 = Expression.Parameter(typeof(T));
   var parameter2 = Expression.Parameter(typeof(TProperty));

   // turning an expression body into a PropertyInfo is common enough
   // that it's a good idea to extract this to a reusable method
   var member = (MemberExpression)pExpression.Body;
   var propertyInfo = (PropertyInfo)member.Member;

   // use the PropertyInfo to make a property expression
   // for the first parameter (the object)
   var property = Expression.Property(parameter1, propertyInfo);

   // assignment expression that assigns the second parameter (value) to the property
   var assignment = Expression.Assign(property, parameter2);

   // then just build the lambda, which takes 2 parameters, and has the assignment
   // expression for its body
   var setter = Expression.Lambda<Action<T, TProperty>>(
      assignment,
      parameter1,
      parameter2
   );

   return setter.Compile();
}

Another thing you can do is encapsulate them:

public sealed class StrongProperty<TObject, TProperty> {
   readonly PropertyInfo mPropertyInfo;

   public string Name => mPropertyInfo.Name;
   public Func<TObject, TProperty> Get { get; }
   public Action<TObject, TProperty> Set { get; }
   // maybe other useful properties

   internal StrongProperty(
      PropertyInfo pPropertyInfo,
      Func<TObject, TProperty> pGet,
      Action<TObject, TProperty> pSet
   ) {
      mPropertyInfo = pPropertyInfo;
      Get = pGet;
      Set = pSet;
   }
}

And now you can pass these around, similar to delegates, and write code whose logic can vary by property. This gets around the fact that you can't pass properties by reference.

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
0

I've made mixed Rytis I solution and https://stackoverflow.com/a/12423256/254109

private static void SetPropertyValue<T>(Expression<Func<T>> lambda, object value)
    {
        var memberExpression = (MemberExpression)lambda.Body;
        var propertyInfo = (PropertyInfo)memberExpression.Member;
        var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
        var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();

        propertyInfo.SetValue(propertyOwner, value, null);
    }

And call it

SetPropertyValue(() => myStuff.MyProp, newValue);
Community
  • 1
  • 1
xmedeko
  • 7,336
  • 6
  • 55
  • 85
  • Here is a GetProperty solution for nested expressions (multiple dots): https://www.codeproject.com/Articles/733296/Expression-Parsing-and-Nested-Properties – baHI Dec 29 '18 at 10:00
0

Everything is much simpler:

public static Action<T, TValue> GetSetter<T, TValue>(
    Expression<Func<T, TValue>> expression)
{
    var parameter = Expression.Parameter(typeof(TValue), "value");
    var setterLambda = Expression.Lambda<Action<T, TValue>>(
        Expression.Assign(expression.Body, parameter),
        expression.Parameters[0],
        parameter);

    return setterLambda.Compile();
}
Konstantin S.
  • 1,307
  • 14
  • 19
0

That's the idea, i'm worked for me with this code, taking into account the contribution of svick:

public class FactoryEntity<TEntity> where TEntity : Entity, new()

{

private TEntity _Entity;

    public FactoryEntity()
    {
        _Entity = new TEntity();
    }

public TEntity Build()
    {
        if (_Entity.IsValid())
            throw new Exception("_Entity.Id");

        return _Entity;
    }

public FactoryEntity<TEntity> AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> foreignEntity, TProperty instanceEntity) where TProperty : Entity
    {
        if (instanceEntity == null || instanceEntity.IsTransient())
            throw new ArgumentNullException();

        SetObjectValue<TEntity, TProperty>(_Entity, foreignEntity, instanceEntity);
        return this;
    }

private void SetObjectValue<T, TResult>(object target, Expression<Func<T, TResult>> expression, TResult value)
    {
        var memberExpression = (MemberExpression)expression.Body;
        var propertyInfo = (PropertyInfo)memberExpression.Member;
        var newValue = Convert.ChangeType(value, value.GetType());
        propertyInfo.SetValue(target, newValue, null);
    }
}

Here I call the factory for me to build the Person object in a valid

Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address).Build();

But I do not know if this code is optimal or not, at least I do not make a call to compile() method, what are saying?

thanks

Emilio Montes
  • 355
  • 1
  • 3
  • 6