ViewModels and one-to-many relationships with Entity Framework in MVC?

I have an application for storing information about consultants in a database. The model is an Entity Framework model, and the database tables are the “Consultant” with a one-to-many relationship to a number of other tables (WorkExperiences, Programs, CompetenceAreas, etc.). Now that I want to create a new Consultant object in the view, I would just like to pass the Consultant object as a model for the view. But for me it was suggested ( A collection of complex child objects in an Asp.Net MVC 3 application? ) That I should not do this, but use ViewModels instead. Secondly, and perhaps for this very reason, I get the error message: "EntityCollection is already initialized" when I try to publish the "Consultant" object, if I use it as a model in the view, and the cause of the error seems to be is a collection of objects such as WorkExperiences.

So my first question is why am I getting this error.

But more importantly, if I have to use ViewModel, how would I do it right? Because I actually tried and earned something. But ... the code is terrible. Can someone please tell me what should I do to get this to work more cleanly?

Let me show you what I have (which works again, but this is a nightmare code):

GET Create Method:

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

A helper method for creating a "ViewModel" (if that's really what should look like a 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 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(); } } 

A helper method for creating a consultant object (this is especially terrible when I have to verify that collections are not null if the user decides not to add anything to these 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, it works again, but is nowhere near as clean as if I could use the consultant object directly (if not for the fact that the "EntityCollection is already initialized" "error" ...). How do I do this?

+4
source share
1 answer

First of all, you should not use the entity object as a representation model, because (and I can think for at least two reasons right now, but there are more of them):

  • You do not want to disclose sensitive data such as "Identifier" or "Password." Imagine that your consultant has an Id , and the evil user opens the editing consultant’s page and sends another Id back. As a result, the evil user will be able to update different Consultant .

  • Currently, everything you show in the view matches what your Consultant object looks like. But if you want to add additional information that is not part of the Consultant object (as simple as a check box). In this case, you need to rewrite quite a bit of code, create a ViewModel, display it, etc. Although, if you follow the ViewModel template from the very beginning, you can just make this simple change when you need it.

As for your code, you can try using AutoMapper with Nested AutoMapper for this type of conversion. Even if you do not, your code can be made a little cleaner using predictions.

 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; } 
+4
source

Source: https://habr.com/ru/post/1340862/


All Articles