Automapper creating new instance rather than map properties
In the meantime there exists an AutoMapper Extension for that particular problem:
cfg.AddCollectionMappers();
cfg.CreateMap<S, D>().EqualityComparison((s, d) => s.ID == d.ID);
With AutoMapper.EF6/EFCore you can also auto generate all equality comparisons. Plaese see AutoMapper.Collection AutoMapper.EF6 or AutoMapper.Collection.EFCore
According to the AutoMapper source file that handles all ICollection (among other things) and the ICollection Mapper:
The collection is cleared by a call to Clear()
then added again, so as far as I can see there is no way that AutoMapper will be able to automagically do the mapping this time.
I would implement some logic to loop over the collections and AutoMapper.Map
the ones that are the same
This is a limitation of AutoMapper as far as I'm aware. It's helpful to keep in mind that while the library is popularly used to map to/from view models and entities, it's a generic library for mapping any class to any other class, and as such, doesn't take into account all the eccentricities of an ORM like Entity Framework.
So, here's the explanation of what's happening. When you map a collection to another collection with AutoMapper, you are literally mapping the collection, not the values from the items in that collection to items in a similar collection. In retrospect, this makes sense because AutoMapper has no reliable and independent way to ascertain how it should line up one individual item in a collection to another: by id? which property is the id? maybe the names should match?
So, what's happening is that the original collection on your entity is entirely replaced with a brand new collection composed of brand new item instances. In many situations, this wouldn't be a problem, but when you combine that with the change tracking in Entity Framework, you've now signaled that the entire original collection should be removed and replaced with a brand new set of entities. Obviously, that's not what you want.
So, how to solve this? Well, unfortunately, it's a bit of a pain. The first step is to tell AutoMapper to ignore the collection completely when mapping:
Mapper.CreateMap<User, UserViewModel>();
Mapper.CreateMap<UserViewModel, User>()
.ForMember(dest => dest.UserPreferences, opts => opts.Ignore());
Notice that I broke this up into two maps. You don't need to ignore the collection when mapping to your view model. That won't cause any problems because EF isn't tracking that. It only matters when you're mapping back to your entity class.
But, now you're not mapping that collection at all, so how do you get the values back on to the items? Unfortunately, it's a manual process:
foreach (var pref in model.UserPreferences)
{
var existingPref = user.UserPreferences.SingleOrDefault(m => m.Id == pref.Id);
if (existingPref == null) // new item
{
user.UserPreferences.Add(Mapper.Map<UserPreference>(pref));
}
else // existing item
{
Mapper.Map(pref, existingPref);
}
}