Проблема с EF STE и самообращающимися таблицами

Это мой первый пост здесь, поэтому я надеюсь, что все в порядке.

Вот моя проблема: в моей базе данных есть таблица с именем UserTypes. Она имеет:

  1. Я БЫ;
  2. IsPrivate;
  3. pARENT_ID;

Соответствующие - первый и третий. У меня есть другая таблица с именем UserTypes_T, которая содержит информацию для различных типов, которая зависит от языка. Поля:

  1. LANGUAGE_ID;
  2. UserType_ID;
  3. Название;

То, что я пытаюсь достичь, это загрузить всю иерархию из таблицы UserTypes и показать ее в TreeView (сейчас это не актуально). Затем, выбрав несколько пользовательских типов, я могу редактировать их в отдельном окне редактирования (имя) и комбинированном окне (родитель).

Все работает нормально, пока я не попытаюсь сохранить изменения в базе данных. EF сгенерировал для меня два класса сущностей для этих таблиц:

Класс для пользовательских типов имеет:

  1. Я БЫ;
  2. IsPrivate;
  3. pARENT_ID;
  4. Навигационное свойство для самостоятельной ссылки (0..1);
  5. Навигационное свойство для дочерних элементов;
  6. Другое навигационное свойство для таблицы UserTypes_T (1..*);

Класс для переведенной информации имеет:

  1. UserType_ID;
  2. LANGUAGE_ID;
  3. Название;
  4. Навигационное свойство к таблице UserTypes (*..1);
  5. Навигационное свойство к таблице Языки (*..1);

Я получаю данные, которые мне нужны, используя:

return context.UserTypes.Include("UserTypes_T").Where(ut => ut.IsPrivate==false).ToList();

в моем веб-сервисе WCF. Я могу добавлять новые типы пользователей без проблем, но когда я пытаюсь обновить старые, происходят некоторые странные вещи.

Если я обновляю корневой элемент (Parent_ID==null), все работает! Если я обновлю элемент, где Parent_ID!= Null, я получу следующую ошибку:

AcceptChanges не может продолжаться, потому что значения ключа объекта конфликтуют с другим объектом в ObjectStateManager.

Я искал по всему Интернету и прочитал сообщение в блоге от Диего Б Вега (и многих других), но моя проблема в другом. Когда я меняю родительский тип пользователя, я на самом деле меняю свойство Parent_ID, а не свойство навигации. Я всегда стараюсь работать с идентификаторами, а не сгенерированными навигационными свойствами, чтобы избежать проблем.

Я провел небольшое исследование, попытался выяснить, что это за граф объектов, и увидел, что там было много повторяющихся объектов:

Корневой элемент имел список своих дочерних элементов. Каждый дочерний элемент имел обратную ссылку на корень или на его родительский элемент и так далее. Ты можешь представить. Поскольку я не использовал эти навигационные свойства, потому что я использовал идентификаторы для получения / установки необходимых мне данных, я удалил их из модели. Чтобы быть точным, я удалил пункты 4 и 5 из класса сущностей UserTypes. Тогда у меня был граф объектов с каждым элементом только один раз. Я пробовал новое обновление, но у меня была та же проблема:

Корневой элемент был обновлен нормально, но элементы, у которых были некоторые родители, бросили то же исключение.

Я обнаружил, что у меня есть свойство навигации в классе сущности UserTypes_T, указывающее на тип пользователя, поэтому я тоже удалил его. Затем эта ошибка исчезла. Все элементы в графе объектов были уникальными. Но проблема осталась - я мог обновить свой корневой элемент без проблем, но при попытке обновить дочерние элементы (без исключений) я получил исключение нулевой ссылки в сгенерированном классе Model.Context.Extensions:

if (!context.ObjectStateManager.TryGetObjectStateEntry(entityInSet.Item2, out entry))
{
    context.AddObject(entityInSet.Item1, entityInSet.Item2);//here!
}

Я пытался обновить только имя (которое находится в UserTypes_T), но ошибка та же.

У меня нет идей, и я пытаюсь решить эту проблему уже 8 часов, поэтому я буду признателен, если кто-нибудь даст мне идеи или поделится своим опытом.

PS:

Единственный способ обновить дочерний объект - использовать следующий код для извлечения данных:

var userTypes = argoContext.UserTypes.Include("UserTypes_T").Where(ut => ut.IsPrivate==false).ToList();
foreach (UserType ut in userTypes)
{
    ut.UserType1 = null;
    ut.UserTypes1 = null;
}
return userTypes;

где UserType1 - это навигационное свойство, указывающее на родительский тип пользователя, а UserTypes1 - навигационное свойство, содержащее список дочернего элемента. Проблема здесь заключалась в том, что EF "исправляет" объекты и изменяет Parent_ID на null. Если я установлю его снова, EF также установит UserTypes1... Может быть, есть способ остановить это поведение?

1 ответ

Решение

Хорошо, все, я только что выяснил, в чем проблема, и выкладываю ответ, если кто-то еще сталкивается с той же проблемой.

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

using (MyEntities context = new MyEntities()) 
{
    string errMsg = MyValidator.ValidateSomething(context.UserTypes,...);
    if (!string.IsNullOrEmpty(errMsg)) throw new FaultException(errMsg);
    //some other code here...
    context.UserTypes.ApplyChanges(_userType);//_userType is the one that is updated
    context.UserTypes.SaveChanges();
}

Проблема заключается в том, что при выполнении проверки контекст заполняется, а при попытке сохранить изменения появляются объекты с одинаковыми значениями ключа.

Решение простое - использовать другой контекст для проверки на сервере:

using (MyEntities validationContext = new MyEntities())
{
    //validation goes here...
}
using (MyEntities context = new MyEntities())
{
    //saving changes and other processing...
}

Еще один может быть:

using (MyEntities context = new MyEntities())
{
    using (MyEntities validationContext = new MyEntities())
    {
        //validation
    }
    //saving changes and other processing...
}

Это оно! Надеюсь это кому-нибудь пригодится!

Другие вопросы по тегам