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?
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]);
});
Try using UseDestinationValue
as described in the answer to this similar question:
Automapper overwrites missing source property on list with child objects
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");