AutoMapper: Losing unmapped property values when m

2019-08-06 06:32发布

问题:

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?

回答1:

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]);
        });


回答2:

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



回答3:

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");