自动映射器创建新实例而不是映射属性 [英] Automapper creating new instance rather than map properties

查看:62
本文介绍了自动映射器创建新实例而不是映射属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个很长的

因此,我有一个模型和一个视图模型,这些模型和视图模型是根据AJAX请求进行更新的. Web API控制器接收到视图模型,然后我使用AutoMapper更新现有模型,如下所示:

So, I have a model and a viewmodel that I'm updating from an AJAX request. Web API controller receives the viewmodel, which I then update the existing model using AutoMapper like below:

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;
}

我已经为用户-> UserViewModel(和后向)映射配置了自动映射器.

I have automapper configured like so for the User -> UserViewModel (and back) mapping.

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

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

(Note that explicitly setting the opposite map and omitting the ReverseMap exhibits the same behavior)

我遇到了Model/ViewModel成员的问题,该成员是另一个对象的ICollection:

I'm having an issue with a member of the Model/ViewModel that is an ICollection of a different object:

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

相应的模型如下:

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


问题:

每个属性均正确映射.当这些集合从ViewModel映射到Model而不是映射属性时,将从ViewModel创建UserPreference对象的新实例,而不是使用ViewModel属性更新现有对象.

Every property of the User and UserViewModel classes maps correctly, except for the ICollections of UserPreferences/UserPreferenceViewModels shown above. When these collections map from the ViewModel to the Model, rather than map properties, a new instance of a UserPreference object is created from the ViewModel, rather than update the existing object with the ViewModel properties.

型号:

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

And the corresponding 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实体映射到用户时,UserPreferenceViewModels的ICollection也应映射到用户的UserPreferences的ICollection.

When mapping a UserViewModel entity to a User, the ICollection of UserPreferenceViewModels is also mapped the User's ICollection of UserPreferences, as it should.

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

However, when this occurs, the individual UserPreference object's properties such as "DateCreated", "CreatedBy_Id", and "User_Id" get nulled as if a new object is created rather than the individual properties being copied.

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

This is further shown as evidence as when mapping a UserViewModel that has only 1 UserPreference object in the collection, when inspecting the DbContext, there are two local UserPreference objects after the map statement. One that appears to be a new object created from the ViewModel, and one that is the original from the existing model.

如何使自动映射器更新现有Model的成员;而不是从ViewModel的集合中实例化新成员?我在这里做什么错了?

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

Screenshots to demonstrate before/after Mapper.Map()

推荐答案

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

This is a limitation of AutoMapper as far as I'm aware. It's helpful to keep in mind that while the library is popularly used to map to/from view models and entities, it's a generic library for mapping any class to any other class, and as such, doesn't take into account all the eccentricities of an ORM like Entity Framework.

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

So, here's the explanation of what's happening. When you map a collection to another collection with AutoMapper, you are literally mapping the collection, not the values from the items in that collection to items in a similar collection. In retrospect, this makes sense because AutoMapper has no reliable and independent way to ascertain how it should line up one individual item in a collection to another: by id? which property is the id? maybe the names should match?

因此,正在发生的是,您实体上的原始集合已完全替换为由全新项目实例组成的全新集合.在许多情况下,这不是问题,但是当您将其与Entity Framework中的更改跟踪结合使用时,您现在已经发出信号,应删除整个原始集合,并用一组全新的实体替换.显然,这不是您想要的.

So, what's happening is that the original collection on your entity is entirely replaced with a brand new collection composed of brand new item instances. In many situations, this wouldn't be a problem, but when you combine that with the change tracking in Entity Framework, you've now signaled that the entire original collection should be removed and replaced with a brand new set of entities. Obviously, that's not what you want.

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

So, how to solve this? Well, unfortunately, it's a bit of a pain. The first step is to tell AutoMapper to ignore the collection completely when mapping:

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

请注意,我将其分解为两张地图.将映射到视图模型时,您无需忽略集合.这不会造成任何问题,因为EF并未对此进行跟踪.仅当您映射回实体类时才重要.

Notice that I broke this up into two maps. You don't need to ignore the collection when mapping to your view model. That won't cause any problems because EF isn't tracking that. It only matters when you're mapping back to your entity class.

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

But, now you're not mapping that collection at all, so how do you get the values back on to the items? Unfortunately, it's a manual process:

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

这篇关于自动映射器创建新实例而不是映射属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆