ViewModels и отношения один ко многим с Entity Framework в MVC? - PullRequest
4 голосов
/ 22 февраля 2011

У меня есть приложение для хранения информации о консультантах в базе данных. Модель представляет собой модель Entity Framework, а таблицы базы данных являются консультантами с отношениями «один ко многим» с рядом других таблиц (WorkExperiences, Programs, CompetenceAreas и т. Д.). Теперь, когда я хочу создать новый объект консультанта в представлении, я действительно просто хочу передать объект консультанта в качестве модели в представление. Но, например, мне было предложено ( Коллекция сложных дочерних объектов в приложении 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;
    }

Итак, снова это работает, но далеко не так чисто, как если бы я мог использовать объект Консультанта напрямую (если бы не эта ошибка "EntityCollection уже инициализирована" ...). Так как я должен это сделать вместо этого?

1 Ответ

4 голосов
/ 23 февраля 2011

Прежде всего, вы не должны использовать свой объект-сущность в качестве модели представления, потому что (и я могу вспомнить по крайней мере две причины сейчас, но есть и другие):

  1. Вы не хотите предоставлять конфиденциальные данные, такие как «Id» или «Password». Представьте, что у вашего консультанта есть Id, а злой пользователь открывает страницу консультанта по редактированию и отправляет обратно другой Id. В результате злой пользователь сможет обновить различные Consultant.

  2. В настоящее время все, что вы показываете в представлении, соответствует тому, как выглядит ваш Consultant объект. Но в случае, если вы хотите добавить дополнительную информацию, которая не является частью объекта Consultant (так просто, как поле флажка). В этом случае вам придется переписать довольно много кода, создать ViewModel, отобразить его и т. Д. Хотя, следуя шаблону ViewModel с самого начала, вы можете просто сделать это простое изменение, когда вам это нужно.

Что касается вашего кода - вы можете попытаться использовать AutoMapper с Nested 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;
}  
...