首页 文章

Automapper创建新实例而不是 Map 属性

提问于
浏览
10

这是一个漫长的过程 .

所以,我有一个模型和一个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;
}

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

Mapper.CreateMap<User, UserViewModel>().ReverseMap();

(注意,显式设置相反的 Map 并省略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; }
}

The Problem:

除了上面显示的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<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的User的ICollection,就像它应该的那样 .

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

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

How can I make automapper update an existing Model's collection;s members, rather than instantiate new members from the ViewModel's collection? What am I doing wrong here?

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

Before

After

2 回答

  • 2

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

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

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

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

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

    请注意,我把它分成了两张 Map . 映射到视图模型时,无需忽略该集合 . 那赢得了跟踪 . 只有当您映射回实体类时才重要 .

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

    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);
        }
    }
    
  • 14

    根据AutoMapper source file处理所有ICollection(以及其他内容)和ICollection Mapper

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

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

相关问题