2015-08-20 107 views
7

這是一個很長的實例。創建新實例而不是映射屬性的自動映射器

所以,我有一個模型和一個viewmodel,我從AJAX請求更新。網絡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; 
} 

我已經automapper配置像這樣的用戶 - > UserViewModel(和背面)的映射。

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

(請注意,顯式設置相對地圖和省略ReverseMap表現出相同的行爲)

我在與模型/視圖模型的構件,其不同的對象的ICollection的一個問題:

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

的相應的模型是像這樣:

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

問題:

用戶和UserViewModel類的每個屬性映射正確地,除了以上所示的使用UserPreferences/UserPreferenceViewModels的ICollections。當這些集合從ViewModel映射到Model而不是映射屬性時,將從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; } 
} 

和相應的視圖模型

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實體到用戶,UserPreferenceViewModels的ICollection的是還它應該像映射用戶的UserPreferences的ICollection一樣。

但是,如果發生這種情況,單個UserPreference對象的屬性(如「DateCreated」,「CreatedBy_Id」和「User_Id」)將被忽略,就像創建新對象而不是被複制的單個屬性一樣。

這進一步顯示爲證據,因爲在映射UserViewModel時,集合中只有1個UserPreference對象,當檢查DbContext時,映射語句後有兩個本地UserPreference對象。一個看起來是從ViewModel創建的新對象,另一個來自現有模型的原始對象。

如何讓automapper更新現有Model的集合的成員,而不是從ViewModel集合中實例化新成員?我在這裏做錯了什麼?

截圖演示之前/之後的Mapper。地圖()

Before

After

+0

您可能需要重寫'UserPreference'和'UserPreferenceViewModel'中的'GetHashCode',以便集合可以識別它們是相同的。也許automapper有一些魔術,但我很懷疑 – HaroldHues

+0

是的,必須複製/粘貼錯誤的行CreateMap線。我正在查看當前的GetHashCode覆蓋,如果發現任何內容,我會報告回來。謝謝你的幫助。 –

+0

您需要退後一步並停止使用AutoMapper從ViewModel映射到您的實體或域模型。 AutoMapper沒有爲此做,並不會按預期工作。使用AutoMapper ** ONLY **從實體/域模型映射到ViewModel/DTO/BindingModel **,從不**其他方式。這樣做是由AutoMapper作者 – Tseng

回答

9

這是AutoMapper的限制,只要我知道。請注意,雖然圖書館普遍用於映射視圖模型和實體的映射,但它是將任何類映射到任何其他類的通用庫,因此不會考慮所有的偏心像實體框架這樣的ORM。

所以,下面是對發生的事情的解釋。使用AutoMapper將集合映射到另一個集合時,實際上將集合映射到另一個集合,而不是將該集合中的項目的值映射到類似集合中的項目。回想起來,這是有道理的,因爲AutoMapper沒有可靠和獨立的方法來確定它應該如何將集合中的一個單獨項目排成另一個:by id?哪個屬性是id?也許名字應該匹配?

因此,發生的事情是,您的實體上的原始集合完全被全新的物品實例組成的集合取代。在許多情況下,這不會是一個問題,但是當您將它與Entity Framework中的更改跟蹤結合使用時,您現在已經發出信號表示應刪除整個原始集合並用一組全新的實體替換。顯然,那不是你想要的。

那麼,該如何解決?那麼,不幸的是,這有點痛苦。第一步是告訴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

據處理所有的ICollection(除其他事項外)的AutoMapper source fileICollection Mapper

收集是通過調用清除爲Clear()然後再加入,所以就我所見,AutoMapper無法自動地進行映射。

我會實現一些邏輯來遍歷集合和AutoMapper.Map的那些是相同的