I use AutoMapper 10.1.1 to map a DTO to a view model. The view model already exists and needs to be updated with the new DTO, so I use the overload of Map that accepts the source and destination objects. I want AutoMapper to update the destination object, but it should not modify objects referenced by the properties of the destination object. Those objects should be treated by AutoMapper as immutable.
To clarify, several of the properties are simple types (e.g. Guid
, int
, string
, etc.) and several of the properties are complex types (i.e. other DTO classes). Mapping the simple types is, well, simple. But mapping the properties containing complex types can be done in two different ways: AutoMapper could update the sub-properties of the existing object referenced by the main object, or it could map a new object from the source property. It appears UseDestinationValue
is intended to control this behavior, but it doesn't seem to do anything:
When UseDestinationValue = true
, I expect AutoMapper to do something similar to this, which is exactly what it does:
destinationObject.ComplexProperty.A = sourceObject.ComplexProperty.A;
destinationObject.ComplexProperty.B = sourceObject.ComplexProperty.B;
destinationObject.ComplexProperty.C = sourceObject.ComplexProperty.C;
When UseDestinationValue = false
, I expect AutoMapper to do something similar to this:
destinationOjbect.ComplexProperty = new ComplexPropertyType() {
A = sourceObject.ComplexProperty.A,
B = sourceObject.ComplexProperty.B,
C = sourceObject.ComplexProperty.C };
I need the latter behavior.
There is very little documentation on UseDestinationValue
and it appears to be widely misunderstood, but note the following sentence from this link:
UseDestinationValue tells AutoMapper not to create a new object for some member, but to use the existing property of the destination object.
If UseDestinationValue()
tells it not to create a new object for some member, then by default it should create a new object for each member. This is precisely the behavior I need, but it doesn't seem to work. That same link above states the default was previously UseDestinationValue = true
(implying the default is now false
). But even if I explicitly specify DoNotUseDestinationValue()
, AutoMapper still modifies the existing member of the destination object rather than creating a new object.
Am I missing something or can you confirm this is a bug in AutoMapper?
Here is a minimal repro:
public class Widget
{
public int Id { get; set; }
}
public class WidgetClone
{
public int Id { get; set; }
}
public class WidgetHolder
{
public int Id { get; set; }
public Widget Widget { get; set; }
}
public class WidgetHolderClone
{
public int Id { get; set; }
public WidgetClone Widget { get; set; }
}
[TestClass]
public class DoNotUseDestinationValueTest
{
[TestMethod]
public void MapToSameType()
{
var mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Widget, Widget>();
cfg.CreateMap<WidgetHolder, WidgetHolder>().ForMember(wh => wh.Widget, opt => opt.DoNotUseDestinationValue());
}).CreateMapper();
var widgetHolder1 = new WidgetHolder() { Id = 7, Widget = new Widget() { Id = 77 } };
var widgetHolder2 = new WidgetHolder() { Widget = new Widget() { Id = 88 } };
var widget2 = widgetHolder2.Widget;
mapper.Map(widgetHolder1, widgetHolder2);
Assert.AreEqual(7, widgetHolder2.Id); // PASS
Assert.AreEqual(77, widgetHolder2.Widget.Id); // PASS
Assert.AreEqual(88, widget2.Id); // FAIL!
}
[TestMethod]
public void MapToDifferentType()
{
var mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Widget, WidgetClone>();
cfg.CreateMap<WidgetHolder, WidgetHolderClone>().ForMember(wh => wh.Widget, opt => opt.DoNotUseDestinationValue());
}).CreateMapper();
var widgetHolder = new WidgetHolder() { Id = 7, Widget = new Widget() { Id = 77 } };
var widgetHolderClone = new WidgetHolderClone() { Widget = new WidgetClone() { Id = 88 } };
var widgetClone = widgetHolderClone.Widget;
mapper.Map(widgetHolder, widgetHolderClone);
Assert.AreEqual(7, widgetHolderClone.Id); // PASS
Assert.AreEqual(77, widgetHolderClone.Widget.Id); // PASS
Assert.AreEqual(88, widgetClone.Id); // FAIL!
}
}
I reviewed all of the following questions and none of them address this issue:
- Automapper UseDestinationValue not working
- Automapper UseDestinationValue
- AutoMapper and "UseDestinationValue"
- UseDestinationValue only when destination property is not null
- Automapper - can it map over only existing properties in source and destination objects?
- Automapper Parent-Child mapping Ignore and UseDestinationValue C#
- Automapper : UseDestinationValue() and Ignore() methode aren't working
- AutoMapper Update UseDestinationValue Not Working as expected on Virtual Properties
- AutoMapper cannot mapping Reference or using UseDestinationValue
- Automapper: UseDestinationValue does not work for collections?