Ошибка «EntityCollection уже инициализирована» с сущностью в качестве модели в Asp.Net MVC? - PullRequest
2 голосов
/ 24 февраля 2011

У меня большие трудности с созданием сложного объекта. У меня есть модель EF с таблицей консультантов, которая имеет отношение «один ко многим» с рядом других таблиц. Я хотел использовать объект «Консультант» в качестве модели, как есть, потому что это было бы очень просто и легко (и как это сделано в учебном пособии NerdDinner), как я делал с другими объектами, которые не имели отношения один ко многим , Проблема заключается в том, что эти отношения вызывают эту ошибку: «EntityCollection уже инициализирован», когда я пытаюсь выполнить публикацию в метод Create.

Несколько человек посоветовали мне использовать ViewModel вместо этого, и я опубликовал вопрос об этом ( ViewModels и отношения один-ко-многим с Entity Framework в MVC? ), потому что я не совсем понимаю Это. Проблема в том, что код становится невероятно нелепым ... Это далеко от простоты, которую я до сих пор ценил в MVC.

В этом вопросе я забыл упомянуть, что, кроме метода Create, метод Edit использует тот же метод CreateConsultant (имя может вводить в заблуждение, оно фактически заполняет объект Консультант). И поэтому, чтобы не добавлять дополнительные, скажем, «Программы», добавленные при редактировании, мне нужно было еще больше усложнить этот метод. Итак, теперь это выглядит так:

private Consultant CreateConsultant(ConsultantViewModel vm, Consultant consultant) //Parameter Consultant needed because an object may already exist from Edit method.
        {
            consultant.Description = vm.Description;
            consultant.FirstName = vm.FirstName;
            consultant.LastName = vm.LastName;
            consultant.UserName = User.Identity.Name;

            if (vm.Programs != null)
                for (int i = 0; i < vm.Programs.Count; i++)
                {
                    if (consultant.Programs.Count == i)
                        consultant.Programs.Add(vm.Programs[i]);
                    else
                        consultant.Programs.ToList()[i] = vm.Programs[i];
                }
            if (vm.Languages != null)
                for (int i = 0; i < vm.Languages.Count; i++)
                {
                    if (consultant.Languages.Count == i)
                        consultant.Languages.Add(vm.Languages[i]);
                    else
                        consultant.Languages.ToList()[i] = vm.Languages[i];
                }
            if (vm.Educations != null)
                for (int i = 0; i < vm.Educations.Count; i++)
                {
                    if (consultant.Educations.Count == i)
                        consultant.Educations.Add(vm.Educations[i]);
                    else
                        consultant.Educations.ToList()[i] = vm.Educations[i];
                }
            if (vm.WorkExperiences != null)
                for (int i = 0; i < vm.WorkExperiences.Count; i++)
                {
                    if (consultant.WorkExperiences.Count == i)
                        consultant.WorkExperiences.Add(vm.WorkExperiences[i]);
                    else
                        consultant.WorkExperiences.ToList()[i] = vm.WorkExperiences[i];
                }

            if (vm.CompetenceAreas != null)
                for (int i = 0; i < vm.CompetenceAreas.Count; i++)
                {
                    if (consultant.CompetenceAreas.Count == i)
                        consultant.CompetenceAreas.Add(vm.CompetenceAreas[i]);
                    else
                        consultant.CompetenceAreas.ToList()[i] = vm.CompetenceAreas[i];
                }

            string uploadDir = Server.MapPath(Request.ApplicationPath) + "FileArea\\ConsultantImages\\";
            foreach (string f in Request.Files.Keys)
            {
                var filePath = Path.Combine(uploadDir, Path.GetFileName(Request.Files[f].FileName));
                if (Request.Files[f].ContentLength > 0)
                {
                    Request.Files[f].SaveAs(filePath);
                    consultant.Image = filePath;
                }

            }
            return consultant;
        }

Это абсурд, и, вероятно, это из-за моей некомпетентности, но мне нужно знать, как это сделать правильно. Просто ответа «использовать ViewModel», очевидно, будет недостаточно, потому что именно это и привело меня к этой проблеме с самого начала. Мне нужна простота простого объекта-сущности в качестве модели, но без ошибки «EntityCollection уже был инициализирован». Как мне обойти это?

Конечно, если я просто неправильно выполняю стратегию ViewModel, предложения по этому вопросу тоже приветствуются, но в основном я хочу знать, что вызывает эту ошибку, если я делаю это простым способом "NerdDinner", простым объектом. Также имейте в виду, что рассматриваемый вид предназначен только для авторизованных пользователей сайта. Я хотел бы сделать это "правильным" способом, но если использование ViewModels подразумевает наличие кода, который так сложно поддерживать, я воздержусь от него ...

Пожалуйста, помогите!

UPDATE:

Оказывается, этот код даже не работает. Я только что проверил после вызова Edit для обновления значений, и он не обновляет их. Так что работает только часть Создать.

Эта часть не работает:

consultant.Programs.ToList()[i] = vm.Programs[i];

У меня была догадка, что я не смог использовать ToList и обновить элемент в EntityCollection. Но это делает это еще сложнее. Так что теперь я не знаю, как сделать это с сущностью напрямую, что я бы предпочел (см. Выше). И я не знаю, как заставить все это работать с ViewModel, не говоря уже о том, чтобы очистить его ...

Есть идеи? Здесь должно быть что-то действительно не так, и я надеюсь, что кто-то заметит, как я только что пропустил что-то простое, что переворачивает весь этот код с ног на голову!

1 Ответ

1 голос
/ 25 февраля 2011

Хорошо, поэтому, по моему опыту, материал EF не всегда легко воспроизводится при использовании по проводам, полная остановка (то есть, это не очень хороший формат для передачи данных, будь то MVC, WCF или что-то еще) , Для меня это проблема EF, а не проблема MVC, потому что проблема, с которой вы сталкиваетесь при непосредственном использовании ваших моделей EF, связана с тем, как они отслеживаются в объектном контексте EF. Мне сказали, что есть решения для этого, включающие повторное присоединение переданного объекта, но я обнаружил, что это будет больше проблем, чем оно того стоит; как и другие, именно поэтому большинство людей говорят «используйте viewmodels» для ваших входных параметров в этой ситуации.

Я согласен, что ваш код выше довольно неприятен, есть несколько способов исправить это. Во-первых, посмотрите AutoMapper , который очень помогает в том, что вы можете пропустить определение очевидного (например, когда обе модели имеют простое свойство, такое как «Имя» одного типа и имени.

Во-вторых, то, что вы делаете с циклами, все равно немного не так. Вы не должны предполагать, что ваш список публикуется с клиента того же порядка и т. Д., Что и список, который отслеживает EF (скорее списки; ссылаясь на ваши программы и т. Д.). Вместо этого подумайте о возможных сценариях и объясните их. Я буду использовать Программы в качестве примера.

1) У консультанта есть программа, но подробности об этой программе изменились. 2) у консультанта нет программы, которая находится в viewmodel 2a) программа уже существует где-то в базе данных, но не является частью этого консультанта 2б) программа новая. 3) у консультанта есть программа, которой нет в модели представления (то, чего вы в данный момент не учитываете).

Я не знаю, что вы хотите, чтобы происходило в каждом из этих случаев (т. Е. Является ли модель представления полной и канонической, или это модель сущности во время обновления?), Но, допустим, вы зацикливаетесь на модели представления Программы, как вы делаете выше. Для сценария 1 (который, кажется, является вашей главной проблемой), вы захотите сделать что-то вроде (используя AutoMapper):

var updated = vm.Programs[i];    
var original = consultant.Programs.SingleOrdefault(p=>p.ID == uppdated.ID);
Mapper.Map(updated,original);
yourEfContext.SaveChanges(); // at some point after this, doesn't have to be inside the loop

EDIT: Еще одна мысль, которая может оказаться полезной для вас, заключается в том, что я, когда вы извлекаете своего консультанта из базы данных (не уверен, какой механизм вы используете для этого), обязательно вызовите функцию Включить в коллекции, которые вы также собираетесь обновить. В противном случае каждая из этих итераций станет очередным обходом базы данных, чего, очевидно, можно было бы избежать, если бы вы просто использовали «Включить», чтобы загружать их все за один раз.

...