3

Using AutoMapper: When mapping nested collections, I expect any unmapped properties to have their original values retained. Instead, they are being set to null.

Example:
I have these four classes
(note that Test2Child has the Name property, while Test1Child does not):

public class Test1
{
    public List<Test1Child> Children { get; set; }
}
public class Test2
{
    public List<Test2Child> Children { get; set; }
}
public class Test1Child
{
    public int Value { get; set; }
}
public class Test2Child
{
    public string Name { get; set; }
    public int Value { get; set; }
}

...and a simple mapping setup.

Mapper.CreateMap<Test1, Test2>();
Mapper.CreateMap<Test1Child, Test2Child>().ForMember(m => m.Name, o => o.Ignore());
Mapper.AssertConfigurationIsValid();    // Ok

I want the original value of Test2Child.Name to be retained during mapping.... I expect this to be the default behaviour for any unmapped properties.

When I map directly from Test1Child to Test2Child, it works fine; Value is mapped and Name is retained:

var a = new Test1Child {Value = 123};
var b = new Test2Child {Name = "fred", Value = 456};
Mapper.Map(a, b);
Assert.AreEqual(b.Value, 123);    // Ok
Assert.AreEqual(b.Name, "fred");  // Ok

When the mapping is for a nested collection (List<Test1Child> to List<Test2Child>),
Value is mapped correctly... but the original value for Name is lost!

var c = new Test1 { Children = new List<Test1Child> { new Test1Child { Value = 123 } } };
var d = new Test2 { Children = new List<Test2Child> { new Test2Child { Name = "fred", Value = 456 } } };
Mapper.Map(c, d);
Assert.AreEqual(d.Children[0].Value, 123);    // Ok
Assert.AreEqual(d.Children[0].Name, "fred");  // FAILS! Name is null.

How do I fix this?

Merenzo
  • 5,326
  • 4
  • 31
  • 46

3 Answers3

2

As mentioned in comments of @MightyMuke's answer, @PatrickSteele makes a good point here: maybe it doesn't make sense to automatically map each item from the source list to the destination list.... i.e. "But what if one list has 3 and the other list has 5?"

In my case, I know that the source and dest lists will always have the same length, and (importantly) the Nth item in the source list is always the direct counterpart to the Nth item in the dest list.

So, this works, but I don't feel good about myself....

Mapper.CreateMap<Test1, Test2>()
    .ForMember(m => m.Children, o => o.Ignore())
    .AfterMap((src, dest) =>
        {
            for (var i = 0; i < dest.Children.Count; i++)
                Mapper.Map(src.Children[i], dest.Children[i]);
        });
Community
  • 1
  • 1
Merenzo
  • 5,326
  • 4
  • 31
  • 46
0

Try using UseDestinationValue as described in the answer to this similar question: Automapper overwrites missing source property on list with child objects

Community
  • 1
  • 1
Mightymuke
  • 5,094
  • 2
  • 31
  • 42
  • Thanks @Mightymuke: That question is eerily similar to mine, and only 18 hours old! Looks like I need to work on my google-fu :) I'll give it a try now... – Merenzo Jan 09 '13 at 08:01
  • I tried this: `Mapper.CreateMap().ForMember(m => m.Children, o => o.UseDestinationValue());` as per your suggestion, but no effect. @PatrickSteele makes a good point [here](http://stackoverflow.com/questions/9724901): "But what if one list as 3 and the other list has 5?" i.e. maybe what I'm trying to do doesn't make sense? – Merenzo Jan 09 '13 at 08:23
0

I ran into the same issue. Basically automapper doesn't know what the key(s) are in your list object in order to match them with the original, so it's going to new up an object. If you want the properties to remain, you need to help it understand how to match back to your original item so you can just map the changes. In order to do this, you'll need to have a key, which you don't have right now.

Try something like the following:

public class Test1
{
    public List<Test1Child> Children { get; set; }
}
public class Test2
{
    public List<Test2Child> Children { get; set; }
}
public class Test1Child
{
    public int ChildId { get; set; }
    public int Value { get; set; }
}
public class Test2Child
{
    public int ChildId { get; set; }
    public string Name { get; set; }
    public int Value { get; set; }

    public Test2Child() 
    { }

    public Test2Child(int childId)
    {
        // of course you will need to load this from your data source, but for testing.  :)
        if (childId == 1)
        {
            ChildId = 1;
            Name = "fred";
            Value = 456;
        }
    }
}

Mapper.CreateMap<Test1, Test2>();
Mapper.CreateMap<Test1Child, Test2Child>()
    .ConstructUsing(t => t.ChildId > 0 ? new Child(t.ChildId) : new Child())
    .ForMember(m => m.Name, o => o.Ignore());

Mapper.AssertConfigurationIsValid();

var c = new Test1 { Children = new List<Test1Child> { new Test1Child { ChildId = 1, Value = 123 } } };
var d = new Test2 { Children = new List<Test2Child> { new Test2Child { ChildId = 1, Name = "fred", Value = 456 } } };
Mapper.Map(c, d);
Assert.AreEqual(d.Children[0].Value, 123);
Assert.AreEqual(d.Children[0].Name, "fred");
Dave
  • 897
  • 8
  • 8
  • Thanks @Dave - yes I think you understand my problem perfectly. However in my particular situation, I am (1) retrieving from the repository, **then (2) changing a bunch of property values** (specifically, I am setting values that I have read in from a web form), then (3) calling `Map()`. By hitting the repository again during `Map()`, I am losing the property values I set during the second step. – Merenzo Feb 04 '13 at 01:22