4

我有一個應用程序來存儲有關顧問在數據庫中的信息。該模型是一個實體框架模型,而數據庫表是Consultant,與許多其他表(WorkExperiences,Programs,CompetenceAreas等)有一對多的關係。現在,當我想在View中創建一個新的Consultant對象時,我真的只想將一個Consultant對象作爲模型傳遞給View。但其中之一,我建議(Collection of complex child objects in Asp.Net MVC 3 application?),我不應該這樣做,但使用ViewModels代替。其次,也許這就是原因,當我嘗試發佈Consultant對象時,如果在視圖中將它用作模型,我會收到一條錯誤,指出「EntityCollection已經被初始化」,並且錯誤的原因似乎是諸如WorkExperiences之類的對象集合。ViewModels和MVC中與實體框架的一對多關係?

所以我的第一個問題是爲什麼我得到這個錯誤。

但更重要的是,如果我應該改用ViewModel,我該如何正確地做到這一點?因爲我實際上已經嘗試了一些東西,並且讓它工作。但是...代碼很糟糕。任何人都可以告訴我,我應該做什麼,而不是讓這個工作更乾淨?

讓我告訴你我有什麼(即再次工作,但它是一個噩夢codewise):

一開始創建方法:

public ActionResult Create() 
    { 
     Consultant consultant = new Consultant(); 
     ConsultantViewModel vm = GetViewModel(consultant); 

     return View(vm); 
    } 

助手方法來創建「視圖模型」(如果這其實是在什麼視圖模型應該是等):

private ConsultantViewModel GetViewModel(Consultant consultant) 
    { 
     ConsultantViewModel vm = new ConsultantViewModel(); 
     vm.FirstName = consultant.FirstName; 
     vm.LastName = consultant.LastName; 
     vm.UserName = consultant.UserName; 
     vm.Description = consultant.Description; 

     vm.Programs = consultant.Programs.ToList(); 
     vm.Languages = consultant.Languages.ToList(); 
     vm.Educations = consultant.Educations.ToList(); 
     vm.CompetenceAreas = consultant.CompetenceAreas.ToList(); 
     vm.WorkExperiences = consultant.WorkExperiences.ToList(); 
     return vm; 
    } 

的POST Create方法:

[HttpPost] 
    [ValidateInput(false)] //To allow HTML in description box 
    public ActionResult Create(ConsultantViewModel vm, FormCollection collection) 
    { 
     try 
     { 
      Consultant consultant = CreateConsultant(vm); 
      _repository.AddConsultant(consultant); 
      _repository.Save(); 
      return RedirectToAction("Index"); 
     } 
     catch 
     { 
      return View(); 
     } 
    } 

Helper方法來創建一個顧問對象(這是一個可怕的特別,在那裏我有檢查的集合不爲空,如果用戶決定不在這些列表添加任何東西...):

private Consultant CreateConsultant(ConsultantViewModel vm) 
    { 
     Consultant consultant = new Consultant(); 
     consultant.Description = vm.Description; 
     consultant.FirstName = vm.FirstName; 
     consultant.LastName = vm.LastName; 
     consultant.UserName = vm.UserName; 

     if (vm.Programs != null) 
      foreach (var program in vm.Programs) 
       consultant.Programs.Add(program); 
     if (vm.Languages != null) 
      foreach (var language in vm.Languages) 
       consultant.Languages.Add(language); 
     if (vm.Educations != null) 
      foreach (var education in vm.Educations) 
       consultant.Educations.Add(education); 
     if (vm.WorkExperiences != null) 
      foreach (var workExperience in vm.WorkExperiences) 
       consultant.WorkExperiences.Add(workExperience); 
     if (vm.CompetenceAreas != null) 
      foreach (var competenceArea in vm.CompetenceAreas) 
       consultant.CompetenceAreas.Add(competenceArea); 

     return consultant; 
    } 

因此,它仍然有效,但遠不如干淨,因爲如果我可以直接使用Consultant對象(如果不是這樣「EntityCollection已經初始化」錯誤「...)。那我應該怎麼做呢?

回答

4

首先,你不應該用你的實體對象的視圖模型,因爲(和現在我能想到的至少有兩個原因,但也有更多):

  1. 你不想要公開敏感數據,例如'Id'或'Password'。想象一下,你的顧問有一個Id,一個邪惡的用戶打開編輯顧問頁面並回傳一個不同的Id。因此,惡意用戶將成功更新不同的Consultant

  2. 目前無論您在視圖中顯示的是否與Consultant對象的外觀相對應。但是,如果您想添加不屬於Consultant對象的額外信息(就像複選框字段一樣簡單)。在這種情況下,您必須重寫相當多的代碼,創建ViewModel,映射它等等。如果您從一開始就遵循ViewModel模式,則只需在需要時進行此簡單更改即可。

關於你的代碼 - 你可以嘗試使用AutoMapperNested Mappings這種類型的轉換。即使你不這樣做,你的代碼可以通過使用投影變得更清晰。

private ConsultantViewModel GetViewModel(Consultant consultant) 
{ 
    return new ConsultantViewModel 
       { 
        FirstName = consultant.FirstName, 
        LastName = consultant.LastName, 
        ... 
        vm.Programs = consultant.Programs.ToList(), 
        ... 
       }; 
} 

private Consultant CreateConsultant(ConsultantViewModel vm) 
{ 
    var consultant = new Consultant 
         { 
          Description = vm.Description, 
          FirstName = vm.FirstName, 
          ... 
         }; 

    if (vm.Programs != null) 
    { 
     vm.Programs.ForEach(consultant.Programs.Add); 
    } 
    ... 

    return consultant; 
} 
+0

好的,謝謝。但是爲了扮演魔鬼的擁護者(爲了理解):如果我沒有把Consultant對象的「EntityCollection已經初始化」錯誤(我仍然不明白)作爲模型,那麼代碼將會如此之多簡單。關於id,我可以將id字段留在視圖中,對吧? (我一直在使用其他上下文環境中的對象時這樣做,有點像NerdDinner的例子)。 – Anders 2011-02-24 09:20:26

+0

至於添加額外的屬性,我有時使用分部類來完成(在屬性只需要應用程序邏輯,而不是數據庫的情況下)...所以我有點了解ViewModels的想法,但在這種情況下仍然不夠。 – Anders 2011-02-24 09:22:12

+0

@Anders - 關於將Id從視圖中移出 - 這不會阻止惡意用戶發佈它!而且由於'DOES'這個模型有一個ID,所以模型綁定器會很高興地把它拿起來,所以你會'有'在那裏有一個潛在的安全漏洞。 – Yakimych 2011-02-24 10:15:56