【问题标题】:ViewModels and one-to-many relationships with Entity Framework in MVC?ViewModels 和与 MVC 中的实体框架的一对多关系?
【发布时间】:2011-02-22 20:57:32
【问题描述】:

我有一个应用程序,用于在数据库中存储有关顾问的信息。该模型是一个实体框架模型,数据库表是与许多其他表(WorkExperiences、Programs、CompetenceAreas 等)具有一对多关系的顾问。现在,当我想在视图中创建一个新的顾问对象时,我真的只想将一个顾问对象作为模型传递给视图。但一方面,有人建议我 (Collection of complex child objects in Asp.Net MVC 3 application?) 我不应该这样做,而是使用 ViewModels。其次,也许这就是原因,当我尝试发布顾问对象时,如果将其用作视图中的模型,我收到一条错误消息“EntityCollection 已被初始化”,错误的原因似乎是对象的集合,例如 WorkExperiences。

所以我的第一个问题是为什么会出现这个错误。

但更重要的是,如果我应该改用 ViewModel,我该如何正确地做到这一点?因为我实际上已经尝试了一些东西,并且让它工作了。但是......代码很糟糕。谁能告诉我我应该怎么做才能让这个工作更干净?

让我向您展示我所拥有的(这同样有效,但在代码方面是一场噩梦):

GET 创建方法:

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

        return View(vm);
    }

创建“ViewModel”的辅助方法(如果这实际上是 ViewModel 应该是的样子):

    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 创建方法:

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

创建顾问对象的辅助方法(这个方法特别糟糕,我必须检查集合是否不为空,以防用户决定不在这些列表中添加任何内容......):

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

所以,它再次起作用,但远不如我可以直接使用顾问对象那样干净(如果不是因为“EntityCollection 已初始化”错误”......)。所以我应该怎么做?

【问题讨论】:

    标签: entity-framework collections asp.net-mvc-3 viewmodel one-to-many


    【解决方案1】:

    首先,您不应该将实体对象用作视图模型,因为(我现在至少可以想到两个原因,但还有更多):

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

    【讨论】:

    • 好的,谢谢。但只是为了扮演魔鬼的拥护者(为了理解):如果我没有得到Consultant对象作为模型的“EntityCollection is already initialized”错误(我仍然不明白),那么代码就会很多更简单。关于 id,我可以将 id 字段留在视图之外,对吧? (在其他环境中使用对象时,我总是这样做,有点像 NerdDinner 示例)。
    • 至于添加额外的属性,我有时会使用部分类来这样做(如果该属性仅用于应用程序逻辑,而不是数据库)......所以我有点了解 ViewModel 的概念,但在这种情况下仍然不完全。
    • @Anders - 关于将 ID 排除在视图之外 - 这不会阻止恶意用户发布它!并且由于 Model DOES 有一个 Id,modelbinder 会很乐意接受它,所以你 WILL 有一个潜在的安全漏洞。
    • @Anders - 关于其他评论 - 是的,你可以做到,而且代码会更简单,而且它实际上可能是 RAD(快速应用程序开发)的一个很好的解决方案。然而,更简单的代码并不一定意味着更好的代码。在架构方面,您将域对象与演示对象(视图模型)混合在一起。再说一遍 - 是的,您可以使用部分类添加属性,但您不能排除您想要真正保护的属性。
    • @chiccodoro - 如果您想要一个真实的示例,假设您有一个系统,其中包含用户可以添加或更新的某种条目。创建时,条目具有自动设置的CreationDate,并且永远不会更改。它不会出现在更新表单上,但在更新条目时,用户可以发布新的CreationDate 以及其余数据,并通过这样做来伪造日志。
    猜你喜欢
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-24
    • 1970-01-01
    相关资源
    最近更新 更多