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