Automapper创建新实例而不是地图属性

这是一个漫长的过程。

所以,我有一个模型和一个viewmodel,我正在从AJAX请求更新。 Web API控制器接收viewmodel,然后我使用AutoMapper更新现有模型,如下所示:

private User updateUser(UserViewModel entityVm) { User existingEntity = db.Users.Find(entityVm.Id); db.Entry(existingEntity).Collection(x => x.UserPreferences).Load(); Mapper.Map(entityVm, existingEntity); db.Entry(existingEntity).State = EntityState.Modified; try { db.SaveChanges(); } catch { throw new DbUpdateException(); } return existingEntity; } 

我为User – > UserViewModel(和back)映射配置了自动配置。

 Mapper.CreateMap().ReverseMap(); 

(注意,明确设置相反的地图并省略ReverseMap表现出相同的行为)

我遇到的Model / ViewModel成员是一个不同对象的ICollection问题:

 [DataContract] public class UserViewModel { ... [DataMember] public virtual ICollection UserPreferences { get; set; } } 

相应的模型是这样的:

 public class User { ... public virtual ICollection UserPreferences { get; set; } } 

问题:

除了上面显示的UserPreferences / UserPreferenceViewModel的ICollections之外,User和UserViewModel类的每个属性都能正确映射。 当这些集合从ViewModel映射到Model而不是map属性时,将从ViewModel创建UserPreference对象的新实例,而不是使用ViewModel属性更新现有对象。

模型:

 public class UserPreference { [Key] public int Id { get; set; } public DateTime DateCreated { get; set; } [ForeignKey("CreatedBy")] public int? CreatedBy_Id { get; set; } public User CreatedBy { get; set; } [ForeignKey("User")] public int User_Id { get; set; } public User User { get; set; } [MaxLength(50)] public string Key { get; set; } public string Value { get; set; } } 

和相应的ViewModel

 public class UserPreferenceViewModel { [DataMember] public int Id { get; set; } [DataMember] [MaxLength(50)] public string Key { get; set; } [DataMember] public string Value { get; set; } } 

和automapper配置:

 Mapper.CreateMap().ReverseMap(); //also tried explicitly stating map with ignore attributes like so(to no avail): Mapper.CreateMap().ForMember(dest => dest.DateCreated, opts => opts.Ignore()); 

将UserViewModel实体映射到User时,UserPreferenceViewModels的ICollection也会映射到用户的UserPreferences ICollection,就像它应该的那样。

但是,当发生这种情况时,单个UserPreference对象的属性(如“DateCreated”,“CreatedBy_Id”和“User_Id”)将被取消,就像创建新对象而不是复制的各个属性一样。

这进一步显示为在映射集合中只有1个UserPreference对象的UserViewModel时的证据,在检查DbContext时,map语句后面有两个本地UserPreference对象。 一个看似是从ViewModel创建的新对象,一个是现有模型的原始对象。

如何使automapper更新现有Model的集合; s成员,而不是从ViewModel的集合中实例化新成员? 我在这做错了什么?

在Mapper.Map()之前/之后演示的屏幕截图

之前

后

据我所知,这是AutoMapper的限制。 记住,虽然库通常用于映射到视图模型和实体,但它是一个通用库,用于将任何类映射到任何其他类,因此,不考虑所有的怪癖。像entity framework这样的ORM。

所以,这是对正在发生的事情的解释。 使用AutoMapper将集合映射到另一个集合时,您实际上是将集合映射,而不是映射集合中的项目中的值到类似集合中的项目。 回想起来,这是有道理的,因为AutoMapper没有可靠和独立的方式来确定它应该如何将集合中的一个单独项目排列到另一个:id? 哪个属性是id? 也许名字应该匹配?

所以,正在发生的事情是,您实体上的原始集合完全被一个由全新项目实例组成的全新集合所取代。 在许多情况下,这不会是一个问题,但是当您将其与entity framework中的更改跟踪相结合时,您现在已经发出信号,表示应删除整个原始集合并替换为一组全新的实体。 显然,这不是你想要的。

那么,如何解决这个问题呢? 好吧,不幸的是,这有点痛苦。 第一步是告诉AutoMapper在映射时完全忽略该集合:

 Mapper.CreateMap(); Mapper.CreateMap() .ForMember(dest => dest.UserPreferences, opts => opts.Ignore()); 

请注意,我把它分成了两张地图。 映射到视图模型时,无需忽略该集合。 这不会导致任何问题,因为EF没有跟踪它。 只有在您映射回实体类时才有意义。

但是,现在你根本没有映射那个集合,那么如何将值重新放回到项目上呢? 不幸的是,这是一个手动过程:

 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(pref)); } else // existing item { Mapper.Map(pref, existingPref); } } 

根据处理所有ICollection(以及其他内容)的AutoMapper源文件和ICollection Mapper :

通过调用Clear()然后再次添加来Clear()该集合,因此,据我所知,此次AutoMapper无法自动执行映射。

我将实现一些逻辑来循环集合和AutoMapper.Map相同的那些