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

Ron*_*gan 13 c# mapping 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<UserViewModel, User>(entityVm, existingEntity);
    db.Entry(existingEntity).State = EntityState.Modified;

    try
    {
        db.SaveChanges();
    }
    catch
    { 
        throw new DbUpdateException(); 
    }

    return existingEntity;
}
Run Code Online (Sandbox Code Playgroud)

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

Mapper.CreateMap<User, UserViewModel>().ReverseMap();
Run Code Online (Sandbox Code Playgroud)

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

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

[DataContract]
public class UserViewModel
{
    ...
    [DataMember]
    public virtual ICollection<UserPreferenceViewModel> UserPreferences { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

相应的模型是这样的:

public class User
{
    ...
    public virtual ICollection<UserPreference> UserPreferences { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

问题:

除了ICollections上面显示的UserPreferences/UserPreferenceViewModels 之外,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; }
}
Run Code Online (Sandbox Code Playgroud)

和相应的ViewModel

public class UserPreferenceViewModel
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    [MaxLength(50)]
    public string Key { get; set; }

    [DataMember]
    public string Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

和automapper配置:

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());
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

之前

后

Chr*_*att 17

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

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

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

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

Mapper.CreateMap<User, UserViewModel>();
Mapper.CreateMap<UserViewModel, User>()
    .ForMember(dest => dest.UserPreferences, opts => opts.Ignore());
Run Code Online (Sandbox Code Playgroud)

请注意,我把它分成了两张地图.映射到视图模型时,无需忽略该集合.这不会导致任何问题,因为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);
    }
}
Run Code Online (Sandbox Code Playgroud)


r2d*_*2d2 5

同时,针对该特定问题存在一个AutoMapper扩展:

cfg.AddCollectionMappers();
cfg.CreateMap<S, D>().EqualityComparison((s, d) => s.ID == d.ID);
Run Code Online (Sandbox Code Playgroud)

使用AutoMapper.EF6 / EFCore,您还可以自动生成所有相等比较。请参见AutoMapper.Collection AutoMapper.EF6AutoMapper.Collection.EFCore