Проблема с EF STE и самообращающимися таблицами
Это мой первый пост здесь, поэтому я надеюсь, что все в порядке.
Вот моя проблема: в моей базе данных есть таблица с именем UserTypes. Она имеет:
- Я БЫ;
- IsPrivate;
- pARENT_ID;
Соответствующие - первый и третий. У меня есть другая таблица с именем UserTypes_T, которая содержит информацию для различных типов, которая зависит от языка. Поля:
- LANGUAGE_ID;
- UserType_ID;
- Название;
То, что я пытаюсь достичь, это загрузить всю иерархию из таблицы UserTypes и показать ее в TreeView (сейчас это не актуально). Затем, выбрав несколько пользовательских типов, я могу редактировать их в отдельном окне редактирования (имя) и комбинированном окне (родитель).
Все работает нормально, пока я не попытаюсь сохранить изменения в базе данных. EF сгенерировал для меня два класса сущностей для этих таблиц:
Класс для пользовательских типов имеет:
- Я БЫ;
- IsPrivate;
- pARENT_ID;
- Навигационное свойство для самостоятельной ссылки (0..1);
- Навигационное свойство для дочерних элементов;
- Другое навигационное свойство для таблицы UserTypes_T (1..*);
Класс для переведенной информации имеет:
- UserType_ID;
- LANGUAGE_ID;
- Название;
- Навигационное свойство к таблице UserTypes (*..1);
- Навигационное свойство к таблице Языки (*..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...
}
Это оно! Надеюсь это кому-нибудь пригодится!