Исключение с Entity Framework 6, отключенным контекстом и отношением "многие ко многим"
У меня проблема с EF6 при использовании "отключенных" контекстов. Сначала я использую базу данных и таким образом смоделировал отношение многие ко многим (за показанным отношением есть таблица соединений с двумя FK, которые вместе составляют составной PK для таблицы соединений. Других столбцов в этом нет. распределительная таблица):
Поскольку я хочу использовать EF автономно (недолговечные контексты, готовые к веб-API), я также реализовал метод "рисования состояния" из книги Джули Лерман и Роуэн Миллерс о DbContext. Конкретно метод, который они описывают в главе 4 книги "Запись исходных значений" (стр.102 и далее). Это мой метод ApplyChanges:
public static void ApplyChangesInGraph<TEntity>(TEntity root) where TEntity : class, IObjectWithState
{
using (var context = new NovaEntities2())
{
context.Set<TEntity>().Add(root);
foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
if (stateInfo.State == State.Unchanged)
{
ApplyPropertyChanges(entry.OriginalValues, stateInfo.OriginalValues);
}
}
context.SaveChanges();
}
}
Имея это в виду, я получаю исключение со следующим тестом:
[Fact]
public void ShouldNotResultInAnInsertOfPlaceOfEmployment()
{
ResetDbToKnownState();
Employee employee;
using (var context = new NovaEntities2())
{
employee = context.Employees
//.Include(e => e.PlacesOfEmployment) // If enabled, an exception is thrown.
.First();
}
employee.Name = "A new name";
NovaEntities2.ApplyChangesInGraph(employee);
}
Если я включаю.Include выше, происходит следующее исключение:
Произошла ошибка System.Data.Entity.Infrastructure.DbUpdateExceptionAn при сохранении сущностей, которые не предоставляют свойства внешнего ключа для своих отношений. Свойство EntityEntries вернет значение NULL, поскольку один объект не может быть определен как источник исключения. Обработка исключений при сохранении может быть упрощена путем предоставления свойств внешнего ключа в типах объектов. Смотрите InnerException для подробностей.
InnerException: System.Data.SqlClient.SqlExceptionViolation ограничения PRIMARY KEY 'PK_EmployeePlaceOfEmployment'. Невозможно вставить повторяющийся ключ в объект 'dbo.EmployeePlaceOfEmployment'. Дубликат значения ключа (140828, 14). То есть. с добавленным выше.Include EF выдает обновление Employee (отлично) И вставку уже существующего PlaceOfEmployment (не так хорошо) для простого случая выше, где я пытаюсь обновить только имя Employee.
Я не могу понять, почему здесь происходит ВСТАВКА, нарушающая первичный ключ. Я попытался пройти через foreach в методе ApplyChanges и убедился, что все состояния объекта установлены правильно. Действительно, на первой итерации сущность "Сотрудник" начинается с "Добавлен" и заканчивается в состоянии "Модифицировано". Затем загруженный объект PlaceOfEmployment обрабатывается, и он начинается как "Добавлен" и заканчивается в состоянии "Неизменен". Тем не менее, INSERT все еще генерируется, что приводит к исключению.
Из профилировщика SQL:
1 ответ
Я не знаю этот метод "Рисование состояния", но я не удивлен, что ваш пример не работает.
С context.Set<TEntity>().Add(root)
происходят три вещи:
- Корень (=
employee
) вAdded
государство - Все дети (=
employee.PlacesOfEmployment
) находятся вAdded
государство - Все записи отношений между корнем и потомками находятся в
Added
государство
Когда вы перебираете context.ChangeTracker.Entries
вы выбираете только сущности из контекста, но не записи отношений (которые являются отдельными артефактами, хранящимися в контексте). В этом цикле вы сбрасываете состояние root и потомков в Modified
или же Unchanged
соответственно. Но это только изменение этого состояния сущности. Состояния отношений все еще Added
и эти состояния переводятся в INSERT
в "таблицу отношений". Вот почему вы получаете исключение дубликата ключа, потому что записи для связи корня и потомков уже есть.
Хотя основной ObjectContext
позволяет получить доступ к записям отношений кроме записей состояния сущности, и я сомневаюсь, что это поможет, потому что как ApplyChangesInGraph
метод распознает, что граф, который передается в метод, имеет добавленные или удаленные или неизмененные отношения, если у вас есть только одна State
собственность на единицу?
Теперь вы, вероятно, могли бы заставить свой метод тестирования работать, если вы используете context.Set<TEntity>().Attach(root);
вместо ...Add(root)...
потому что это поставит все сущности и отношения в Unchanged
состояние (или - я считаю - он не будет хранить какие-либо записи отношений в контексте вообще). В результате не должно произойти никакого INSERT в таблицу ссылок (или DELETE из этой таблицы). Однако, я боюсь, что с ...Attach
ApplyChangesInGraph
метод больше не будет работать правильно, если у вас есть граф объектов с действительно новыми (или удаленными) отношениями, где вы действительно ожидаете INSERT (или DELETE) для таблицы ссылок.
Честно говоря, я понятия не имею, как вы могли бы реализовать этот универсальный метод, чтобы он работал в целом для всех возможных сценариев изменений. Мне кажется, что граф объектов (с одним State
свойство на сущность) просто не содержит достаточно информации, чтобы описать все изменения отношений, которые могли произойти, когда граф был отключен. (Мне было бы интересно, как Джули Лерман и Роуэн Миллер предлагают реализовать это.)