omu.valueinjecter deep clone unlike types
Asked Answered
M

2

7

I think I'm missing a simple concept with valueinjecter and/or AutoMapper, but how do you deep clone a parent dto.Entity to biz.Entity and include all children?

For example, biz.person.InjectFrom(dto.person). I want the dto.person.AddressList collection to copy down to biz.person.AddressList collection, even though dto.Address and biz.Address are unlike types, but have the same property names.

My thinking is that if the Parent property names are spelled the same, e.g. AddressList, then it wouldn't matter if the 2 underlying objects were of different types. It would still copy same-named simple types like int, string, etc.

thank you

Minority answered 23/11, 2011 at 22:22 Comment(3)
have you looked at the Deep Cloning page from ValueInjecter's codeplex page ? valueinjecter.codeplex.com/…Ruination
Hey Chuck. Yes I did. It didn't deep clone my [nhibernate] child entitiesMinority
it would be nice if you would post some code, and about different types, the default InjectFrom() injects from same name and same type, so it's not going to affect members with different types (and if you are doing from one type to another that's not cloning anymore, that's why the DeepClone didn't worked for you )Ruination
L
8

I was having the same issue when the arrays/lists in the objects have the same names but different types (ie a property named Animals of type ORMAnimals[] mapping to a property named Animals of type Animals[]).

With some minor tweaks to the sample code Chuck Norris has on the Deep Cloning page I got it working in my test code:

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)
            || c.TargetProp.Type.IsValueType || c.TargetProp.Type == typeof(string))
            return c.SourceProp.Value;

        //handle arrays
        if (c.SourceProp.Type.IsArray)
        {
            var arr = c.SourceProp.Value as Array;
            var clone = Activator.CreateInstance(c.TargetProp.Type, arr.Length) 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(c.TargetProp.Type.GetElementType()).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.TargetProp.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.TargetProp.Type)
            .InjectFrom<CloneInjection>(c.SourceProp.Value);
    }
}
Ligure answered 1/6, 2012 at 0:14 Comment(4)
addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e); Has anyone done this? I've tried by replacing 'var list' with 'dynamic list' and then doing list.Add(e), which compiles but throws a runtime exception.Athallia
@Ligure - Tried this but I kept getting stack overflow :(Doucette
@Doucette is it possible one of your objects has a reference back to itself or another parent object causing an infinite loop while traversing the children?Ligure
@Ligure - Yes this was the case and I haven't been able to figure it out. I'm using Entity Framework 5 without proxy's or lazy loading. Is there a way to define a stopping point any time there is a circular reference?Doucette
D
1

I had this issue, even using the CloneInjection wasn't working to copy the properties with same name and diferent types. So I changed a few things in the CloneInjection (I'm using ValueInjecter version 3.1.3).

public class CloneInjection : LoopInjection
{
    protected override void Execute(PropertyInfo sp, object source, object target)
    {
        var tp = target.GetType().GetProperty(sp.Name);
        if (tp == null) return;
        var val = sp.GetValue(source);
        if (val == null) return;

        tp.SetValue(target, GetClone(sp, tp, val));
    }

    private static object GetClone(PropertyInfo sp, PropertyInfo tp, object val)
    {
        if (sp.PropertyType.IsValueType || sp.PropertyType == typeof(string))
        {
            return val;
        }

        if (sp.PropertyType.IsArray)
        {
            var arr = val as Array;
            var arrClone = arr.Clone() as Array;

            for (int index = 0; index < arr.Length; index++)
            {
                var a = arr.GetValue(index);
                if (a.GetType().IsValueType || a is string) continue;

                arrClone.SetValue(Activator.CreateInstance(a.GetType()).InjectFrom<CloneInjection>(a), index);
            }

            return arrClone;
        }

        if (sp.PropertyType.IsGenericType)
        {
            //handle IEnumerable<> also ICollection<> IList<> List<>
            if (sp.PropertyType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
            {
                var genericType = tp.PropertyType.GetGenericArguments()[0];

                var listType = typeof(List<>).MakeGenericType(genericType);
                var list = Activator.CreateInstance(listType);

                var addMethod = listType.GetMethod("Add");
                foreach (var o in val as IEnumerable)
                {
                    var listItem = genericType.IsValueType || genericType == typeof(string) ? o : Activator.CreateInstance(genericType).InjectFrom<CloneInjection>(o);
                    addMethod.Invoke(list, new[] { listItem });
                }

                return list;
            }

            //unhandled generic type, you could also return null or throw
            return val;
        }

        return Activator.CreateInstance(tp.PropertyType)
            .InjectFrom<CloneInjection>(val);
    }
}

I used like this:

var entityDto = new EntityDto().InjectFrom<CloneInjection>(sourceEntity);

I hope it helps!

Doucet answered 15/8, 2018 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.