这是一个很长的问题。
所以,我有一个模型和一个viewmodel,我正在从AJAX请求更新。 Web API控制器接收视图模型,然后我使用AutoMapper更新现有模型,如下所示:
private User updateUser(UserViewModel entityVm)
{
User existingEntity = db.Users.Find(entityVm.Id);
db.Entry(existingEntity).Collection(x => x.UserPreferences).Load();
Mapper.Map<UserViewModel, User>(entityVm, existingEntity);
db.Entry(existingEntity).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch
{
throw new DbUpdateException();
}
return existingEntity;
}
我为用户配置了自动配置程序 - &gt; UserViewModel(和返回)映射。
Mapper.CreateMap<User, UserViewModel>().ReverseMap();
(注意,明确设置相反的地图并省略ReverseMap表现出相同的行为)
我遇到了Model / ViewModel成员的问题,该成员是另一个对象的ICollection:
[DataContract]
public class UserViewModel
{
...
[DataMember]
public virtual ICollection<UserPreferenceViewModel> UserPreferences { get; set; }
}
相应的模型是这样的:
public class User
{
...
public virtual ICollection<UserPreference> UserPreferences { get; set; }
}
问题:
User和UserViewModel类的每个属性都正确映射,但上面显示的UserPreferences / UserPreferenceViewModel的ICollections
除外。当这些集合从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; }
}
自动播放器配置:
Mapper.CreateMap<UserPreference, UserPreferenceViewModel>().ReverseMap();
//also tried explicitly stating map with ignore attributes like so(to no avail):
Mapper.CreateMap<UserPreferenceViewModel, UserPreference>().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()
之前/之后演示的屏幕截图答案 0 :(得分:16)
据我所知,这是AutoMapper的限制。记住,虽然图书馆通常用于映射到视图模型和实体,但它是一个通用的库,用于将任何类映射到任何其他类,因此,它不会#39 ; t考虑像实体框架这样的ORM的所有怪癖。
所以,这里是对正在发生的事情的解释。使用AutoMapper将集合映射到另一个集合时,您实际上是将集合映射,而不是将该集合中的项目中的值映射到类似集合中的项目。回想起来,这是有道理的,因为AutoMapper没有可靠和独立的方式来确定它应该如何将集合中的一个单独项目排列到另一个:id?哪个属性是id?也许名字应该匹配?
所以,正在发生的事情是,您实体上的原始集合完全被一个由全新项目实例组成的全新集合所取代。在许多情况下,这不会成为问题,但是当您将其与实体框架中的更改跟踪结合使用时,您现在已经发出信号,表示应删除整个原始集合并替换为全新的集合。实体。显然,这不是你想要的。
那么,如何解决这个问题呢?嗯,不幸的是,这有点痛苦。第一步是告诉AutoMapper在映射时完全忽略该集合:
Mapper.CreateMap<User, UserViewModel>();
Mapper.CreateMap<UserViewModel, User>()
.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<UserPreference>(pref));
}
else // existing item
{
Mapper.Map(pref, existingPref);
}
}
答案 1 :(得分:2)
根据处理所有ICollection(以及其他内容)的AutoMapper source file和ICollection Mapper:
通过调用Clear()
清除该集合,然后再次添加,因此,据我所知,此次AutoMapper无法自动执行映射。
我会实现一些逻辑来循环集合,AutoMapper.Map
那些是相同的
答案 2 :(得分:2)
同时,针对该特定问题存在一个AutoMapper扩展:
cfg.AddCollectionMappers();
cfg.CreateMap<S, D>().EqualityComparison((s, d) => s.ID == d.ID);
使用AutoMapper.EF6 / EFCore,您还可以自动生成所有相等比较。 Plaese请参见AutoMapper.Collection AutoMapper.EF6或AutoMapper.Collection.EFCore