0

Use Case

Let's say that you're mapping data from a View Model to a Domain Model. The View Model only contains some of the items within your Domain Model, in order to POST and update partial views, rather than the entire view.

class ViewModel {
     public Guid id { get; set; }
     public string name { get; set; }
     public string address { get; set;}
}

class DomainModel {
     public Guid id { get; set; }
     public string name { get; set; }
     public string address { get; set; }
     public string houseColor { get; set; }
     public virtual Vehicle car { get; set; }
}

Currently, when ViewModel is mapped to DomainModel, name and address will be set properly, but houseColor and car will be overwritten with null.

I have attempted using the following mappings to no effect. After mapping the data, houseColor and car are unintentionally overwritten with null :

var map1 = new MapperConfiguration(
     cfg =>cfg.CreateMap<ViewModel,DomainModel>()
          .ForAllMembers(o =>o.Condition(src =>src != null))
);

var map2 = new MapperConfiguration(
     cfg =>cfg.CreateMap<ViewModel,DomainModel>()
          .ForMember(dest =>dest.houseColor, o =>o.Ignore() )
          .ForMember(dest =>dest.car, o =>o.Ignore() )
);

var map3 = new MapperConfiguration(
     cfg =>cfg.CreateMap<ViewModel,DomainModel>()
          .ForMember(dest =>dest.houseColor, o =>o.UseDestinationValue() )
          .ForMember(dest =>dest.car, o =>o.UseDestinationValue() )
);

var map4 = new MapperConfiguration(
     cfg =>cfg.CreateMap<ViewModel,DomainModel>()
          .ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null))
);

Controller

[HttpPost]
public PartialViewResult _Partial_View(ViewModel VM) {
     var DomainModel = db.DomainModels.Find(VM.Id);

     //calculations to update VM data.
     //Creation of mappings as detailed above.
     
     var mapper = mapName.CreateMapper();
     DomainModel = mapper.Map<ViewModel,DomainModel>(VM);
     db.SaveChanges();
     return PartialView("PartialView.cshtml",VM);
}

Is there any way to prevent AutoMapper from overwriting Domain model values with null, when the equivalent source value is undeclared?

Alternatives

One may simply take the values from the Domain Model, store them in variables, and put them back into the Domain Model after mapping, but this would easily become difficult to maintain, and defeats the purpose of using a mapper.

Additional Notes

A Stack Overflow post has attempted to address this issue, but the post is from September 2014, and uses a long-outdated version of AutoMapper, rendering the answer ineffective. AutoMapper can't prevent null source values if not all source properties match and How to ignore null values for all source members during mapping in Automapper 6?

Community
  • 1
  • 1
Muroxxas
  • 158
  • 1
  • 1
  • 10
  • 3
    Isn't automapper giving you a new instance of DomainModel when you call Map(viewModel)? So it isn't setting them to null, it is creating a new instance which has not had the properties set? How are you calling the mapper? – Simon May 16 '20 at 19:38
  • Unless using the overload of Map that accepts a destination object to fill. – pinkfloydx33 May 16 '20 at 19:41
  • Does this answer your question? [How to ignore null values for all source members during mapping in Automapper 6?](https://stackoverflow.com/questions/43947475/how-to-ignore-null-values-for-all-source-members-during-mapping-in-automapper-6) – pinkfloydx33 May 16 '20 at 19:45
  • @pinkfloydx33 Unfortunatly, that doesn't work, since his question asks about if his source member is null, not if his source member is undeclared entirely. I've added it to the list of examples of mappings I've tried. – Muroxxas May 16 '20 at 20:09
  • @Simon I've added a generic version of my POST function that maps the data from the View Model to the Domain Model, as well as put Id in the VM and DM. Does that help make it clearer? – Muroxxas May 16 '20 at 20:09
  • From the controller code, how does mapper know to update the entity. It is returning a new instance which you're updating your variable to point at the new one. I am not an automapper expert, only just looked into it, but from that code I cannot see how it will update your real database DomainModel variable. It s replacing it with the newly mapped one. – Simon May 16 '20 at 20:20
  • Did you try specifying the MemberLists.Source when creating the map? Not sure if that helps – pinkfloydx33 May 16 '20 at 20:21
  • There's also other overloads of `Condition` that contain the resolution context which perhaps you can use. If all else, you may need to create a custom resolver or use `ConstructUsing`. But now I'm confused, **what are you expecting those properties to contain**? You aren't passing in a pre-constructed object (two parameters to `Map`) and you don't have any default values in your model class that would be replaced... So I *would* expect `null` – pinkfloydx33 May 16 '20 at 20:25

1 Answers1

1

Would this work?

[HttpPost]
public PartialViewResult _Partial_View(ViewModel VM) {
     var DomainModel = db.DomainModels.Find(VM.Id);

     //calculations to update VM data.
     //Creation of mappings as detailed above.

     var mapper = mapName.CreateMapper();
     mapper.Map<ViewModel,DomainModel>(VM, DomainModel);
     db.SaveChanges();
     return PartialView("PartialView.cshtml",VM);
}
Simon
  • 736
  • 6
  • 21
  • Might not need the generics as you are supplying both types – Simon May 16 '20 at 20:22
  • Yes! That works! Thank you so much! I had assumed that by setting Character to the result of the mapping that it would only reassign the value that were in the View Model, rather than recreating a whole Domain model object! – Muroxxas May 16 '20 at 20:29
  • 1
    Glad to help. No, the mapper had no knowledge of an existing entity. You simply asked it to map your viewModel into a domainModel, so it made you a new one. – Simon May 16 '20 at 20:34
  • Yes, the generics are useless. – Lucian Bargaoanu May 17 '20 at 04:25