locked
Expression trees RRS feed

  • Question

  • Hi,
    I'm implementing a property copier however I want to make sure that I only overwrite the properties that aren't null in the destination object and I'm a little confused as to how to do that check:

                    Expression.Block(
                        from property in typeof(T).GetProperties()
                        where SOURCEVALUE != null
                        select Expression.Assign(
                            Expression.Property(destination, property),
                            Expression.Property(source, property)));

    What would I need to replace the SOURCEVALUE != null with to make this work? And if possible how would I convert this Expression.Block and Assign to 3.5?
    Any help is appreciated.

    Thanks
    • Moved by Noam Ben-Ami - MSFT1 Wednesday, October 7, 2009 8:52 PM (From:ADO.NET Entity Framework and LINQ to Entities)
    Wednesday, October 7, 2009 4:12 AM

Answers

  • First of all, you posted your question in the wrong forum - this forum is about entity framework.

    As to your question, instead of giving you the code, I'll teach you the technique - Write your expression as a lambda expression, put it in a Func<T> variable and in debug, use the expression tree visualizer to see what the expression should look like. This way you can understand how to write expression for all sorts of queries

    http://msdn.microsoft.com/en-us/library/bb397975.aspx

    Please mark posts as answers/helpful if it answers your question
    • Marked as answer by mpaulf Thursday, October 8, 2009 6:26 AM
    Wednesday, October 7, 2009 8:21 AM
  • OK, here's an attempt at doing this almost entirely via expression trees...

    1. class TestClass
    2. {
    3.     public object Property1 { get ; set ; }
    4.     public object Property2 { get ; set ; }
    5.     public object Property3 { get ; set ; }
    6.     public object Property4 { get ; set ; }
    7.     public override string ToString()
    8.     {
    9.         return String .Format("Property1: {0}, Property2: {1}, Property3: {2}, Property4: {3}" , this .Property1, this .Property2, this .Property3, this .Property4);
    10.     }
    11. }
    12.  
    13. public class Program
    14. {
    15.     static void Main(string [] args)
    16.     {
    17.         TestClass instance1 = new TestClass { Property1 = 5, Property2 = "Hi" , Property3 = DateTime .Now, Property4 = "String Property" };
    18.         TestClass instance2 = new TestClass { Property1 = 25 };
    19.  
    20.         Console .WriteLine("{0}\n{1}\n" , instance1, instance2);
    21.         var copier = CreatePropertyCopier<TestClass >();
    22.         copier.Compile()(instance1, instance2);
    23.         Console .WriteLine("{0}\n{1}\n" , instance1, instance2);
    24.  
    25.         Console .WriteLine("\nMethod has returned. Press any key to exit..." );
    26.         Console .ReadLine();
    27.     }
    28.  
    29.     static Expression <Action <T, T>> CreatePropertyCopier<T>()
    30.     {
    31.         var s = Expression .Parameter(typeof (T), "source" );
    32.         var d = Expression .Parameter(typeof (T), "destination" );
    33.         var i = Expression .Parameter(typeof (T), "instance" );
    34.  
    35.         var nullRef = Expression .Constant(null , typeof (object ));
    36.         var nullArgs = Expression .Constant(null , typeof (object []));
    37.  
    38.         var list = Expression .Constant(typeof (T).GetProperties().ToList());
    39.         var p = Expression .Parameter(typeof (PropertyInfo ), "property" );
    40.         var getValue = Expression .Lambda<Func <T, object >>(Expression .Call(p, "GetValue" , null , i, nullArgs), i);
    41.  
    42.         var isNull = Expression .Lambda<Func <bool >>(Expression .Equal(nullRef, Expression .Invoke(getValue, d)));
    43.         var where = Expression .Invoke(isNull);
    44.         Expression <Action > doNothing = () => DoNothing();
    45.  
    46.         var ifTrue = Expression .Call(p, "SetValue" , null , d, Expression .Invoke(getValue, s), nullArgs);
    47.         var ifFalse = Expression .Invoke(doNothing);
    48.  
    49.         var forEachAction = Expression .Lambda<Action <PropertyInfo >>(Expression .Condition(where, ifTrue, ifFalse), p);
    50.  
    51.         var copier = Expression .Lambda<Action <T, T>>(Expression .Call(list, "ForEach" , null , forEachAction), s, d);
    52.  
    53.         return copier;
    54.     }
    55.  
    56.     static void DoNothing()
    57.     {
    58.     }
    59. }

    I couldn't determine how to avoid the false branch of the conditional action (line 49). That's left me with the static DoNothing method.
    • Edited by Wole Ogunremi Wednesday, October 7, 2009 9:44 PM corrected significant typo
    • Marked as answer by mpaulf Thursday, October 8, 2009 6:26 AM
    Wednesday, October 7, 2009 9:23 PM

All replies

  • First of all, you posted your question in the wrong forum - this forum is about entity framework.

    As to your question, instead of giving you the code, I'll teach you the technique - Write your expression as a lambda expression, put it in a Func<T> variable and in debug, use the expression tree visualizer to see what the expression should look like. This way you can understand how to write expression for all sorts of queries

    http://msdn.microsoft.com/en-us/library/bb397975.aspx

    Please mark posts as answers/helpful if it answers your question
    • Marked as answer by mpaulf Thursday, October 8, 2009 6:26 AM
    Wednesday, October 7, 2009 8:21 AM
  • Add a method which takes in the propertyInfo and the source object and call the GetValue of the propertyInfo. Use that method in your where clause. Eg
    1. bool IsNullProperty(object instance, PropertyInfo prop)
    2. {
    3.     object result = prop.GetValue(instance, BindingFlags .Public, null , null , null );
    4.     return result == null ;
    5. }

    then assuming the object you're testing against is stored in a variable called destination, define your where clause like
    where IsNullProperty(destination, property)

    Now, I'm unclear as to why or whether you need the expression framework to achieve copying of values. I can suggest an alternative and simpler method:
    1. void CopyNullProperties<T>(T source, T destination)
    2. {
    3.     var nullProperties = (from property in typeof (T).GetProperties()
    4.                          where IsNullProperty(destination, property)
    5.                          select property).ToList();
    6.  
    7.     nullProperties.ForEach(prop =>
    8.     {
    9.         object sourceValue = prop.GetValue(source, BindingFlags .Public, null , null , null );
    10.         prop.SetValue(destination, source, null );
    11.     });
    12. }

    Wednesday, October 7, 2009 9:13 AM
  • Ido: I apologize for posting in the wrong forum and thanks for the link.

    Wole: Thanks for your reply. The code you provided is pretty much the same code that I'm using right now, however I'd like to convert it to expression trees because it is extremely slow - If I only had to copy one object here and there it would be fine, but I'm dealing with hundreds, potentially thousands of objects so I thought expression trees would do that job. I also looked at Reflection.Emit, but it seems I should be able to achieve the same fast object copy with Expression trees.

    Thanks
    Wednesday, October 7, 2009 12:07 PM
  • OK, here's an attempt at doing this almost entirely via expression trees...

    1. class TestClass
    2. {
    3.     public object Property1 { get ; set ; }
    4.     public object Property2 { get ; set ; }
    5.     public object Property3 { get ; set ; }
    6.     public object Property4 { get ; set ; }
    7.     public override string ToString()
    8.     {
    9.         return String .Format("Property1: {0}, Property2: {1}, Property3: {2}, Property4: {3}" , this .Property1, this .Property2, this .Property3, this .Property4);
    10.     }
    11. }
    12.  
    13. public class Program
    14. {
    15.     static void Main(string [] args)
    16.     {
    17.         TestClass instance1 = new TestClass { Property1 = 5, Property2 = "Hi" , Property3 = DateTime .Now, Property4 = "String Property" };
    18.         TestClass instance2 = new TestClass { Property1 = 25 };
    19.  
    20.         Console .WriteLine("{0}\n{1}\n" , instance1, instance2);
    21.         var copier = CreatePropertyCopier<TestClass >();
    22.         copier.Compile()(instance1, instance2);
    23.         Console .WriteLine("{0}\n{1}\n" , instance1, instance2);
    24.  
    25.         Console .WriteLine("\nMethod has returned. Press any key to exit..." );
    26.         Console .ReadLine();
    27.     }
    28.  
    29.     static Expression <Action <T, T>> CreatePropertyCopier<T>()
    30.     {
    31.         var s = Expression .Parameter(typeof (T), "source" );
    32.         var d = Expression .Parameter(typeof (T), "destination" );
    33.         var i = Expression .Parameter(typeof (T), "instance" );
    34.  
    35.         var nullRef = Expression .Constant(null , typeof (object ));
    36.         var nullArgs = Expression .Constant(null , typeof (object []));
    37.  
    38.         var list = Expression .Constant(typeof (T).GetProperties().ToList());
    39.         var p = Expression .Parameter(typeof (PropertyInfo ), "property" );
    40.         var getValue = Expression .Lambda<Func <T, object >>(Expression .Call(p, "GetValue" , null , i, nullArgs), i);
    41.  
    42.         var isNull = Expression .Lambda<Func <bool >>(Expression .Equal(nullRef, Expression .Invoke(getValue, d)));
    43.         var where = Expression .Invoke(isNull);
    44.         Expression <Action > doNothing = () => DoNothing();
    45.  
    46.         var ifTrue = Expression .Call(p, "SetValue" , null , d, Expression .Invoke(getValue, s), nullArgs);
    47.         var ifFalse = Expression .Invoke(doNothing);
    48.  
    49.         var forEachAction = Expression .Lambda<Action <PropertyInfo >>(Expression .Condition(where, ifTrue, ifFalse), p);
    50.  
    51.         var copier = Expression .Lambda<Action <T, T>>(Expression .Call(list, "ForEach" , null , forEachAction), s, d);
    52.  
    53.         return copier;
    54.     }
    55.  
    56.     static void DoNothing()
    57.     {
    58.     }
    59. }

    I couldn't determine how to avoid the false branch of the conditional action (line 49). That's left me with the static DoNothing method.
    • Edited by Wole Ogunremi Wednesday, October 7, 2009 9:44 PM corrected significant typo
    • Marked as answer by mpaulf Thursday, October 8, 2009 6:26 AM
    Wednesday, October 7, 2009 9:23 PM
  • Simple source = new Simple { Value = "test", ValueInt = null, ValueInt2 = 332 };
    Simple target2 = new Simple { Value = "test3", ValueInt = 33, ValueInt2 = 333 };
    Simple target = PropertyCopier3<Simple>.Copy(source, target2);
     
    class Simple
    {
            public string Value { get; set; }
            public int? ValueInt { get; set; }
            public int? ValueInt2 { get; set; }
    }

    Thanks Wole. That's much better than Reflection. I have a version I've been working on as well (with the help of the expression tree visualizer), however I'm getting "The type initializer for 'MiscUtil.Reflection.PropertyCopier3`1' threw an exception", where the inner exception reads: "{"Lambda Parameter not in scope"}". Here is my code:

        public static class PropertyCopier3<T>
        {
            private static readonly Func<T, T, T> copier;
    
            public static T Copy(T source, T target)
            {
                return copier(source, target);
            }
    
            static PropertyCopier3()
            {
                copier = BuildCopier();
            }
    
            private static Func<T, T, T> BuildCopier()
            {
                ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
                ParameterExpression destinationParameter = Expression.Parameter(typeof(T), "target");
                var bindings = new List<MemberBinding>();
                foreach (PropertyInfo sourceProperty in typeof(T).GetProperties())
                {
                    PropertyInfo targetProperty = typeof(T).GetProperty(sourceProperty.Name);
    
                    MemberExpression left = Expression.Property(Expression.Parameter(typeof(T), "source"), sourceProperty.Name);
                    ConstantExpression right = Expression.Constant(null, typeof(object));
                    Expression equality = Expression.Equal(left, right);
    
                    bindings.Add(Expression.Bind(targetProperty,
                                                 Expression.Condition(equality,
                                                                      Expression.Property(destinationParameter, sourceProperty),
                                                                      Expression.Property(sourceParameter, sourceProperty))));
    
                    //bindings.Add(Expression.Bind(targetProperty, 
                    //                                                  //Expression.Property(destinationParameter, sourceProperty),
                    //                                                  Expression.Property(destinationParameter, sourceProperty)));
                }
                Expression initializer = Expression.MemberInit(Expression.New(typeof(T)), bindings);
                Expression<Func<T, T, T>> newFunc = Expression.Lambda<Func<T, T, T>>(initializer, sourceParameter, destinationParameter);
                return newFunc.Compile();
            }
            // }
        }



    The problem seems to be somewhere with the Equality because if I replace that code with the one that's commented out the expression compile just fine.

    and it gets called as follows:



    Any clue what I'm doing wrong?

    Thanks
    Thursday, October 8, 2009 5:51 AM
  • My mistake, got it working.

    Thanks

    Thursday, October 8, 2009 6:26 AM