An entity object cannot be referenced by multiple instances of IEntityChangeTracker

155
10 января 2017, 23:39

Есть 3 сущности - репетитор, студент, группа, все связаны друг с другом через многие ко многим. Решил сделать так: объявляю статическую переменную groupForSave, и в неё ложу группу из БД либо болванку для новой группы. При добавлении нового студента, с помощью ajax добавляю нового студента в список groupForSave.Students, при редактировании соответственно изменяю объект коллекции groupForSave.Students. Фактически все коллекции группы изменяются в groupForSave. В самом конце, при сохранении изменений, вызываю Update/Create передав сформированный groupForSave.

Проблема Не могу создать новую группу, см. код, строчка в самом начале, ошибка падает даже при том, что в группе нету ни одного студента, единственное указан репетитор, остальные связи пустые.

Мои догадки Возможно, когда я беру репетитора контроллер в методе Create из репозитория, каким-то образом его контекст не уничтожается. Пробовал заменить во всех репозиториях private dbontext = new EfDbCOntext() на using получил ошибку, что пробую использовать объект из несуществующего контекста, в том же месте при добавлении группы.

//entity framework часть
class GroupRepository {
    dbContext = new EfDbContext(); //Ошика как-то связана с контекстами
    //упрощено, ошибка при добавлении новой записи
    public Group Create(Group obj) {
        dbContext.Groups.Add(obj); //Здесь падает ошибка
    }
}
/*
... Классы-репозитории с аналогичным методом Create для остальных сущностей(entity)
*/
class Group {
    public virtual List<Tutor> Tutors;
    public virtual List<Student>  Students;
}
class Student {
    public virtual List<Group> Groups;
    public virtual List<Tutor> Tutors;
}
class Tutor {
    public virtual List<Group> Groups;
    public virtual List<Student> Students;
}
    //Контроллер
    GroupController() {
         GroupRepository groupRepository = new GroupRepository();
         TutorRepository tutorRepository = new TutorRepository();
         StudentRepository studentRepository = new StudentRepository();
    }
    [HttpPost]
    public JsonResult AddStudent(Student student)
    {
        //because id is 0 we should define if we need to create new student or it aleady exists in database
        var stud = studentRepository.GetByMail(student.Email);
        if(stud != null)
        {
            student = stud;
        }else
        {
            student.ID = idCounter++;
        }
        groupForSave.Students.Add(student);
        return Json(student);
    }
    public ActionResult Create(int tutorId)
    {
        EditGroupViewModel model = new EditGroupViewModel();
        groupForSave = new Group();
        groupForSave.Journals = new List<Journal>();
        groupForSave.Students = new List<Student>();
        groupForSave.Tutors = new List<Tutor>();
        groupForSave.Tutors.Add(tutorRepository.GetById(tutorId));
        model.name = "Новая группа " + groupRepository.GetTutorGroups(tutorId).Count();
        model.tutorId = tutorId;
        redirectAfterCreate = Request.UrlReferrer.ToString();
        return View("Edit", model);
    }
    public JsonResult Save(Group group)
    {
        //return Json(group, JsonRequestBehavior.AllowGet);
        groupForSave.Name = group.Name;
        groupRepository.CreateWithContext(groupForSave, tutorRepository.Context);
        return Json(new { redirectUrl = redirectAfterCreate }, JsonRequestBehavior.AllowGet);
    }
Answer 1

Всё оказалось довольно просто, хоть и пришлось изрядно погуглить и поотлаживать. Темы сборщик мусора, виртуальные свойства EF, жизненный цикл контроллера ASP MVC 5.

Когда мы берём сущность: var group = dbContext.Groups.First() полученная сущность привязана к dbContext, т.е. он отслеживает объект который предоставил. В сущностях есть виртуальные свойства. Виртуальные свойства сразу не прогружены, в момент вызова group.Tutors происходит обращение к контексту, к которому привязана сущность и из него возвращается коллекция типа Tutor.

 groupForSave.Tutors = new List<Tutor>();
 groupForSave.Tutors.Add(tutorRepository.GetById(tutorId));

таким образом я получил в коллекции Tutors элемент, который завязан на контекст tutorRepository. И поэтому нельзя выполнить groupRepository.Create(groupForSave), ведь в groupRepository есть свой контекст, попытка добавить в него tutor, который завязан на другой контекст приводит к ошибке.

Для исправления я решил, сделать свойство Context в репозиториях:

DbContext Context {
    get{return dbContext;}
    set{this.dbContext = value;}
}

Т.о. перед groupRepository.Create(group) я устанавливаю группам контекст из репетитора. Загвоздка в том, что экземпляр класса контроллера создаётся всякий раз при вызове метода действия, поэтому контекст репетиторской репозитории при добавлении не соответствует контексту репетиторского репозитория, который был использован для начальной инициализации поля group.Tutors. На старый контекст ссылается репетитор,поэтому контекст не был удалён сборщиком мусора, хотя экземпляр контроллера был уничтожен.

Решением стало объявление статической переменной contextForSave, которая инициилизируется нужным контекстом в методе Create и в последствии используется всякий раз при работе с groupRepository. Также этот контекст по тем же причинам используется для studentRepository

кратко это выглядит так:

public class GroupController : Controller
{
    private GroupRepository groupRepository = new GroupRepository();
    private TutorRepository tutorRepository = new TutorRepository();
    private StudentRepository studentRepository = new StudentRepository();
    public static Group groupForSave;
    private static EFDbTutorProject contextForSave;
    public ActionResult Edit(int groupId, int tutorId)
    {
        contextForSave = tutorRepository.Context;
        groupRepository.Context = contextForSave; //as all classes have different context, we need one to detect modified students
        //...
    }

    public ActionResult Create(int groupId, int tutorId)
    {
        contextForSave = tutorRepository.Context;
        groupRepository.Context = contextForSave; //as all classes have different context, we need one to detect modified students
        //...
    }
    public ActionResult AddStudent(Student student)
    {
        //because id is 0 we should define if we need to create new student or it aleady exists in database
        studentRepository.Context = contextForSave;
        var stud = studentRepository.GetByMail(student.Email);
        //...
    }
    public JsonResult Save(Group group)
    {
        //return Json(group, JsonRequestBehavior.AllowGet);
        groupForSave.Name = group.Name;
        groupRepository.CreateWithContext(groupForSave, contextForSave);
        //...
    }
READ ALSO
Добавление колонок в DataGridVew

Добавление колонок в DataGridVew

Добрый день! Мне нужно вывести в DataGridView колонки с именами субъектов (класс Subject) из коллекции subjects, но при этом присвоить колонке в поле Tag идентификатор...

142
Datagridview. Страничное представление

Datagridview. Страничное представление

Как лучше всего организовать страничное представление данных в гриде? Примерно 10 000 строк

84
Обновить сущность в бд по id

Обновить сущность в бд по id

Исключение типа "SystemInvalidOperationException" возникло в EntityFramework

170
С# Microsoft life cam исключение An exception of type &#39;System.Windows.Markup.XamlParseException&#39;

С# Microsoft life cam исключение An exception of type 'System.Windows.Markup.XamlParseException'

У меня появилась проблема мне нужно отобразить изображение с камеры используя api lifecam в качестве примера я выбрал этот код

127