The problem is the country
you are retrieving from database already has some cities. When you use AutoMapper like this:
// mapping
AutoMapper.Mapper.Map(countryDTO, country);
AutoMapper is doing something like creating an IColletion<City>
correctly (with one city in your example), and assigning this brand new collection to your country.Cities
property.
The problem is EntityFramework doesn't know what to do with the old collection of cities.
- Should it remove your old cities and assume only the new collection?
- Should it just merge the two lists and keep both in database?
In fact, EF cannot decide for you. If you want to keep using AutoMapper, you can customize your mapping like this:
// AutoMapper Profile
public class MyProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<CountryData, Country>()
.ForMember(d => d.Cities, opt => opt.Ignore())
.AfterMap(AddOrUpdateCities);
}
private void AddOrUpdateCities(CountryData dto, Country country)
{
foreach (var cityDTO in dto.Cities)
{
if (cityDTO.Id == 0)
{
country.Cities.Add(Mapper.Map<City>(cityDTO));
}
else
{
Mapper.Map(cityDTO, country.Cities.SingleOrDefault(c => c.Id == cityDTO.Id));
}
}
}
}
The Ignore()
configuration used for Cities
makes AutoMapper just keep the original proxy reference built by EntityFramework
.
Then we just use AfterMap()
to invoke an action doing exactly what you thought:
- For new cities, we map from DTO to Entity (AutoMapper creates a new
instance) and add it to country's collection.
- For existing cities, we use an overload of
Map
where we pass the existing entity as the second parameter, and the city proxy as first parameter, so AutoMapper just updates the existing entity's properties.
Then you can keep your original code:
using (var context = new Context())
{
// getting entity from db, reflect it to dto
var countryDTO = context.Countries.FirstOrDefault(x => x.Id == 1).ToDTO<CountryData>();
// add new city to dto
countryDTO.Cities.Add(new CityData
{
CountryId = countryDTO.Id,
Name = "new city",
Population = 100000
});
// change existing city name
countryDTO.Cities.FirstOrDefault(x => x.Id == 4).Name = "another name";
// retrieving original entity from db
var country = context.Countries.FirstOrDefault(x => x.Id == 1);
// mapping
AutoMapper.Mapper.Map(countryDTO, country);
// save and expecting ef to recognize changes
context.SaveChanges();
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…