How to deep clone objects containing an IList property using AutoMapper
Asked Answered
M

2

12

I am trying to deep clone the following class using AutoMapper:

public class MainData
{
    public MainData()
    {
        Details = new List<Detail>();
    }

    public int Id { get; private set; }
    public DateTime LastUpdate { get; private set; }
    public IList<Detail> Details { get; private set; }
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }

    public void AddDetail(Detail detail)
    {
        Details.Add(detail);
    }

    public void RemoveDetail(Detail detail)
    {
        Details.Remove(detail);
    }

    public MainData Clone()
    {
        Mapper.Reset();
        Mapper.CreateMap<MainData, MainData>().ForMember(d => d.Id, o => o.Ignore());
        // Mapper.CreateMap<Detail, Detail>().ForMember(d => d.Id, o => o.Ignore()); // REMOVED
        var newMainData = new MainData();
        Mapper.Map(this, newMainData);
        newMainData.Details = this.Details.Select(item => item.Clone()).ToList(); // ADDED
        return newMainData;
    }
}

public class Detail
{
    public int Id { get; private set; }
    public string Name { get; set; }
    public double Area { get; set; }
    public double Height { get; set; }

    public Detail Clone() // ADDED
    {
        Mapper.CreateMap<Detail, Detail>().ForMember(d => d.Id, o => o.Ignore());
        var newDetail = new Detail();
        Mapper.Map(this, newDetail);
        return newDetail;
    }
}

The Clone method works fine for the MainData properties but seems to only do a shallow copy of the Details list. I have tried adding .ForMember(d => d.Details, o => o.UseDestinationValue()) but this does not copy the Details list at all. How can I get the Details list deep cloned as well ie, so I end up with two totally independent objects including all the list items?

UPDATE: I need to exclude the Id property as I am using these objects with NHibernate so not sure if the Serializable solution will do this.

UPDATE2: Modified the above code to clone the IList too. This seems to work fine as I can exclude properties that make NHibernate think it has already been saved.

Menides answered 3/8, 2010 at 13:16 Comment(0)
J
9

here is one solution with the ValueInjecter

        var clone = new MainData();

        clone.InjectFrom(mainData);//mainData is your source

        mainData.Details.AsParallel.ForAll(detail => 
        {
            var dc = new Detail();
            dc.InjectFrom(detail);
            clone.AddDetail(dc);
        });

the properties that have private setters are not going to be set, (looks reasonable)
good luck ;)

EDIT: I did a better solution look here

Jonathanjonathon answered 3/8, 2010 at 18:17 Comment(2)
Very nice library, works well. Had to change the PLINQ part to a ForEach loop as I am not using .NET 4 yet.Menides
@Piers Myers, I did something more generic for cloning, you can see it here valueinjecter.codeplex.com/…Jonathanjonathon
A
9

AutoMapper isn't really a cloning API. I would instead use this cloning trick:

public static object CloneObject(object obj)
{
    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, 
             new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, obj);
        memStream.Seek(0, SeekOrigin.Begin);
        return binaryFormatter.Deserialize(memStream);
    }
}

It doesn't work for every situation, but it's pretty handy.

Armstrong answered 3/8, 2010 at 13:35 Comment(2)
thanks, I did come across this solution a few times in my searching but wasn't sure I wanted to make all my classes Serializable. The Automapper method looked quite elegant if I can get it to work as I want.Menides
this is a popular hack for cloning. unfortunately it introduces all kinds of issues that come with serialization/deserialization. I was hoping AutoMapper would be more straight forward as well :)Octuple
J
9

here is one solution with the ValueInjecter

        var clone = new MainData();

        clone.InjectFrom(mainData);//mainData is your source

        mainData.Details.AsParallel.ForAll(detail => 
        {
            var dc = new Detail();
            dc.InjectFrom(detail);
            clone.AddDetail(dc);
        });

the properties that have private setters are not going to be set, (looks reasonable)
good luck ;)

EDIT: I did a better solution look here

Jonathanjonathon answered 3/8, 2010 at 18:17 Comment(2)
Very nice library, works well. Had to change the PLINQ part to a ForEach loop as I am not using .NET 4 yet.Menides
@Piers Myers, I did something more generic for cloning, you can see it here valueinjecter.codeplex.com/…Jonathanjonathon

© 2022 - 2024 — McMap. All rights reserved.