This page is now obsolete, please go to the new page:

Faster Deep Cloning using SmartConventionInjection and FastMember


Deep Cloning

this will clone all the properties of the objects in the tree which are of types like: int, string (value types), int? (nullables), Foo, Bar (simple types), IEnumerable<>, ICollection<>, IList<>, List<> (extenders of IEnumerable<T>), Arrays

usage:
var clone = new Foo();
clone.InjectFrom<CloneInjection>(foo);

the injection:
        public class CloneInjection : ConventionInjection
        {
            protected override bool Match(ConventionInfo c)
            {
                return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Value != null;
            }

            protected override object SetValue(ConventionInfo c)
            {
                //for value types and string just return the value as is
                if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
                    return c.SourceProp.Value;

                //handle arrays
                if (c.SourceProp.Type.IsArray)
                {
                    var arr = c.SourceProp.Value as Array;
                    var clone = arr.Clone() as Array;

                    for (int index = 0; index < arr.Length; index++)
                    {
                        var a = arr.GetValue(index);
                        if (a.GetType().IsValueType || a.GetType() == typeof(string)) continue;
                        clone.SetValue(Activator.CreateInstance(a.GetType()).InjectFrom<CloneInjection>(a), index);
                    }
                    return clone;
                }


                if (c.SourceProp.Type.IsGenericType)
                {
                    //handle IEnumerable<> also ICollection<> IList<> List<>
                    if (c.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
                    {
                        var t = c.SourceProp.Type.GetGenericArguments()[0];
                        if (t.IsValueType || t == typeof (string)) return c.SourceProp.Value;

                        var tlist = typeof(List<>).MakeGenericType(t);
                        var list = Activator.CreateInstance(tlist);

                        var addMethod = tlist.GetMethod("Add");
                        foreach (var o in c.SourceProp.Value as IEnumerable)
                        {
                            var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(o);
                            addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e);
                        }
                        return list;
                    }

                    //unhandled generic type, you could also return null or throw
                    return c.SourceProp.Value;
                }

                //for simple object types create a new instace and apply the clone injection on it
                return Activator.CreateInstance(c.SourceProp.Type)
                    .InjectFrom<CloneInjection>(c.SourceProp.Value);
            }
        }

Here's how I've tested it:

       public class Foo
        {
            public string Name { get; set; }
            public int Number { get; set; }
            public int? NullInt { get; set; }
            public Foo F1 { get; set; }
            public IEnumerable<Foo> Foos { get; set; }
            public Foo[] FooArr { get; set; }
            public int[] IntArr { get; set; }
            public IEnumerable<int> Ints { get; set; }
        }

        [Test]
        public void Test()
        {
            var o = new Foo
                        {
                            Name = "foo",
                            Number = 12,
                            NullInt = 16,
                            F1 = new Foo { Name = "foo one" },
                            Foos = new List<Foo>
                                       {
                                           new Foo {Name = "j1"},
                                           new Foo {Name = "j2"},
                                       },
                            FooArr = new Foo[]
                                         {
                                             new Foo {Name = "a1"},
                                             new Foo {Name = "a2"},
                                             new Foo {Name = "a3"},
                                         },
                            IntArr = new[] { 1, 2, 3, 4, 5 },
                            Ints = new[] { 7, 8, 9 },
                        };

            var c = new Foo().InjectFrom<CloneInjection>(o) as Foo;

            Assert.AreEqual(o.Name, c.Name);
            Assert.AreEqual(o.Number, c.Number);
            Assert.AreEqual(o.NullInt, c.NullInt);
            Assert.AreEqual(o.IntArr, c.IntArr);
            Assert.AreEqual(o.Ints, c.Ints);

            Assert.AreNotEqual(o.F1, c.F1);
            Assert.AreNotEqual(o.Foos, c.Foos);
            Assert.AreNotEqual(o.FooArr, c.FooArr);

            //Foo F1
            Assert.AreEqual(o.F1.Name, c.F1.Name);

            //Foo[] FooArr
            Assert.AreEqual(o.FooArr.Length, c.FooArr.Length);
            Assert.AreNotEqual(o.FooArr[0], c.FooArr[0]);
            Assert.AreEqual(o.FooArr[0].Name, c.FooArr[0].Name);

            //IEnumerable<Foo> Foos
            Assert.AreEqual(o.Foos.Count(), c.Foos.Count());
            Assert.AreNotEqual(o.Foos.First(), c.Foos.First());
            Assert.AreEqual(o.Foos.First().Name, c.Foos.First().Name);
        }

Last edited Mar 11, 2013 at 10:41 PM by o, version 3

Comments

DotNetWise Dec 26, 2011 at 8:59 PM 
Activator.CreateInstance<T> is way worse than new T()

I don't even understand why you have the new() restriction since you are not even using it!

kinstephen Oct 1, 2011 at 10:30 PM 
danobri, I ran into a similar issue as well, needing to map and merge back to an entity. Check out a convention I wrote to solve this... http://valueinjecter.codeplex.com/discussions/274484

o Jun 1, 2011 at 7:08 AM 
@Peterson123456 yes,
I did something similar here: http://code.google.com/p/prodinner/source/browse/trunk/Tests/MappingTest.cs
basically I put a parameter bool isChild in the constructor of the valueinjection, and by looking at the value of this parameter I change the behaviour of the injection

Peterson123456 May 30, 2011 at 4:28 PM 
Hi, I have a scenario with parent-child object relationship where child entity holds a reference to the parent. It means that there is a circular reference causing above code failing due to stack overflow. Is there a built in functionality how to tell the ValueInjecter that a property (e.g. "Owner") is a "reference"? I am thinking about some kind of similar solution like we know for the case of serialisation/deserialisation circular reference problem (reference attribute).
Thanks,
Peterson132456

o May 7, 2011 at 1:54 PM 
ok I found what you need, I've answered on stackoverfow: http://stackoverflow.com/questions/5860386/dynamic-type-casting-in-custom-valueinjecter-injection/5921486#5921486

danobri May 4, 2011 at 8:54 PM 
@christophererker - the source and and target both contain ICollection objects, but the collections contain different object types. My target contains a collection of EF generated POCOs, and my source contains a collection of similar looking MVC view model objects. ***However - I think I may have discovered a work around*** I just updated from EF 4.0 to EF 4.1, (now using the DbContext Generator templates instead of POCO Generator templates), and it looks like EF 4.1 is managing change tracking, and thus will work with the CloneInjection. That said - I'd still be interested to know if there is a way to dynamically cast a generic collection...

o May 4, 2011 at 1:49 PM 
@christophererker @danobri it doesn't matter if it's SourceProp or TargetProp .Type, because they are the same when you inject from/to the same type

danobri May 2, 2011 at 5:01 PM 
I got a start on this, and was able to create an injection that will work with the existing target object rather than cloning. However, currently I am casting to a known type, so the code is only good for that one type. Can't seem to figure a way to cast dynamically, any suggestions? Here's the SetValue code for my "RecursiveInjection" class. Note that right now I am only handling generic collection types (not handling arrays).

//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;

if (c.SourceProp.Type.IsGenericType)
{
//handle IEnumerable<> also ICollection<> IList<> List<>
if (c.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
var t = c.TargetProp.Type.GetGenericArguments()[0];
if (t.IsValueType || t == typeof(string)) return c.SourceProp.Value;

//get enumerable object in target
//Type theType = c.TargetProp.Type;

var targetCollection = c.TargetProp.Value as ICollection<MyTargetType>;//possible to cast dynamically?

foreach (var o in c.SourceProp.Value as IEnumerable)
{
//get ID of source object
var sourceID = (int)o.GetProps().GetByName("ID").GetValue(o);
//find matching target object if there is one
var target = targetCollection.SingleOrDefault(x => x.ID == sourceID);
if (target != null)
{
target.InjectFrom<RecursiveInjection>(o);
}
}
return targetCollection;
}
}

If I can figure out a way to dynamically cast using the c.TargetProp.Type value, I think I can work out the additional logic to handle removed and added items.

Thanks, DanO

danobri May 1, 2011 at 1:00 PM 
Omu -

Is there a way to do this kind of recursive injection without cloning? So if the target object already exists, just update the existing object properties rather than creating a new instance of the type and setting the target to the new object? I am struggling with a limitation of Entity Framework (v4) that you can not reattach disconnected complex objects. EF doesn't have the ability to merge unfortunately. So I am hoping to find a way to get VI to handle the merge.

Thanks!
DanO

danobri Apr 28, 2011 at 6:54 PM 
I believe you also need to update this line:

var t = c.SourceProp.Type.GetGenericArguments()[0];

to:

var t = c.TargetProp.Type.GetGenericArguments()[0];

Otherwise it causes a stack overflow. :)

christophererker Apr 11, 2011 at 12:24 AM 
I think the last line should be

return Activator.CreateInstance(c.TargetProp.Type).InjectFrom<CloneInjection>(c.SourceProp.Value);