MVC 中的 ViewModel 和与实体框架的一对多关系?

发布于 2024-10-18 10:09:17 字数 3349 浏览 11 评论 0原文

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

所以我的第一个问题是为什么我会收到此错误。

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

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

GET Create 方法:

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

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

I have an application for storing information about consultants in a database. The model is an Entity Framework model, and the database tables are Consultant with one-to-many relationships to a number of other tables (WorkExperiences, Programs, CompetenceAreas, etc). Now, when I want to create a new Consultant object in a View, I would really just want to pass a Consultant object as the model to the View. But for one, it has been suggested to me (Collection of complex child objects in Asp.Net MVC 3 application?) that I shouldn't do this, but use ViewModels instead. Secondly, and maybe this is the reason, I get an error saying "The EntityCollection has already been initialized" when I try to post the Consultant object if using it as a model in the View, and the cause of the error seems to be the collections of objects such as WorkExperiences.

So my first question is why I'm getting this error.

But more importantly, if I should instead use a ViewModel, how would I do that properly? Because I have in fact tried something, and got it working. But...the code is awful. Can anyone please tell me what I should be doing instead to get this working more cleanly?

Let me show you what I have (that again works, but is a nightmare codewise):

The GET Create method:

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

        return View(vm);
    }

Helper method to create the "ViewModel" (if this is in fact what a ViewModel is supposed to be like):

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

The POST Create method:

    [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 method to create a Consultant object (this one is particularly awful, where I have to check that collections are not null, in case the user decides not to add anything in those lists...):

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

So, again it works, but is nowhere near as clean as if I could have used a Consultant object directly (if not for that "EntityCollection is already initialized" error"...). So how should I do it instead?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

金兰素衣 2024-10-25 10:09:18

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

  1. 您不想公开敏感数据,例如“ID”或“密码”。想象一下,您的顾问有一个 Id,而邪恶的用户打开编辑顾问页面并回发不同的 Id。因此,邪恶的用户将成功更新不同的Consultant

  2. 目前,您在视图中显示的任何内容都与您的 Consultant 对象的外观相对应。但如果您想添加不属于 Consultant 对象的额外信息(就像复选框字段一样简单)。在这种情况下,您必须重写大量代码、创建 ViewModel、映射它等。而如果您从一开始就遵循 ViewModel 模式,则可以在需要时进行这个简单的更改。

关于您的代码 - 您可以尝试将 AutoMapper嵌套映射用于此类转换。即使您不这样做,也可以通过使用投影使您的代码变得更加简洁。

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

First of all, you shouldn't use your entity object as the viewmodel because (and I can think of at least two reasons right now, but there are more):

  1. You don't want to expose sensitive data, such as 'Id' or 'Password'. Imagine your consultant has an Id and an evil user opens the edit consultant page and posts back a different Id. As a result, the evil user will succeed in updating different Consultant.

  2. Currently whatever you show in the View corresponds to what your Consultant object looks like. But in case you want to add extra info that is not part of the Consultant object (as simple as a checkbox field). In that case, you have to rewrite quite a bit of code, create the ViewModel, map it, etc. While if you follow the ViewModel pattern from the start, you can just make this simple change whenever you need it.

Regarding your code - you can try to use AutoMapper with Nested Mappings for this type of conversion. Even if you don't, your code can be made a bit cleaner by using projections.

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;
}  
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文