Я использую Entity Framework 4.3.1 в проекте, сначала использую код и API DbContext. Мое приложение представляет собой n-уровневое приложение, в котором от клиента могут входить отключенные объекты. Я использую SQL Server 2008 R2, но скоро перейду на SQL Azure. Я столкнулся с проблемой, которую просто не могу решить.
Представьте, у меня есть несколько классов:
class A {
// Random stuff here
}
class B {
// Random stuff here
public A MyA { get; set; }
}
class C {
// Random stuff here
public A MyA { get; set; }
}
По умолчанию EF работает с графами объектов. Например, если у меня есть экземпляр B, который инкапсулирует экземпляр A, и я вызываю myDbSet.Add(myB);
, он также помечает экземпляр A как добавленный (при условии, что он еще не отслеживается).
В моем приложении есть сценарий, в котором мне нужно четко указать, какие объекты сохраняются в базе данных, а не отслеживать все графы объектов. Порядок действий следующий:
A myA = new A(); // Represents something already in DB that doesn't need to be udpated.
C myC = new C() { // Represents something already in DB that DOES need to be updated.
A = myA;
}
B myB0 = new B() { // Not yet in DB.
A = myA;
}
B myB1 = new B() { // Not yet in DB.
A = myA;
}
myDbSetC.Attach(myC);
context.Entry(myC).State = Modified;
myDbSetB.Add(myB0); // Tries to track myA with a state of Added
myDbSetB.Add(myB1);
context.SaveChanges();
В этот момент я получаю сообщение об ошибке: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges.
Я полагаю, что это происходит потому, что вызов add на myB0 помечает экземпляр A как добавленный, что конфликтует с экземпляром A, который уже отслеживается.
В идеале я мог бы сделать что-то вроде вызова myDbSet.AddOnly(myB)
, но, очевидно, у нас нет такой возможности.
Я пробовал несколько обходных путей:
Попытка № 1:
Сначала я попытался создать вспомогательный метод для предотвращения добавления myA во второй раз.
private void MarkGraphAsUnchanged<TEntity>(TEntity entity) where TEntity : class {
DbEntityEntry entryForThis = this.context.Entry<TEntity>(entity);
IEnumerable<DbEntityEntry> entriesItWantsToChange = this.context.ChangeTracker.Entries().Distinct();
foreach (DbEntityEntry entry in entriesItWantsToChange) {
if (!entryForThis.Equals(entry)) {
entry.State = System.Data.EntityState.Unchanged;
}
}
}
...
myDbSetB.Add(myB0);
MarkGraphAsUnchanged(myB0);
Хотя это решает проблему попытки добавить myA, оно по-прежнему вызывает ключевые нарушения в ObjectStateManager.
Попытка № 2:
Я попытался сделать то же, что и выше, но установив состояние «Отсоединено» вместо «Без изменений». Это работает для сохранения, но настаивает на установке myB0.A = null
, что имеет другие негативные последствия в моем коде.
Попытка № 3:
Я использовал TransactionScope вокруг всего моего DbContext. Однако даже при вызове SaveChanges()
между каждым Attach()
и Add()
средство отслеживания изменений не сбрасывает отслеживаемые записи, поэтому у меня возникает та же проблема, что и при попытке № 1.
Попытка № 4:
Я продолжил работу с TransactionScope, за исключением того, что использовал шаблон репозитория / DAO и внутренне создал новый DbContext и вызывал SaveChanges()
для каждой отдельной операции, которую я выполняю. В этом случае я получил сообщение об ошибке «Оператор Store update, insert или delete затронул неожиданное количество строк». При использовании SQL Profiler я обнаружил, что при вызове SaveChanges()
в операции second , которую я сделал (первый Add()
), он фактически отправляет UPDATE
SQL в базу данных из первая операция во второй раз - но строки не меняются. Для меня это похоже на ошибку в Entity Framework.
Попытка № 5:
Вместо использования TransactionScope я решил использовать только DbTransaction. Я все еще создаю несколько контекстов, но передаю предварительно созданный EntityConnection каждому новому контексту по мере его создания (путем кэширования и ручного открытия EntityConnection, созданного первым контекстом). Однако, когда я делаю это, во втором контексте запускается определенный мной инициализатор, даже если он уже запустился при первом запуске приложения. В среде разработчиков у меня есть это заполнение некоторых тестовых данных, и на самом деле истекает время ожидания блокировки базы данных для таблицы, которую я впервые изменил Attach()
(но все еще заблокирован из-за открытой транзакции).
Помощь !! Я перепробовал все, что мог придумать, и если не считать полного рефакторинга моего приложения, чтобы не использовать свойства навигации или не использовать вручную созданные DAO для выполнения операторов INSERT, UPDATE и DELETE, я в растерянности. Кажется, должен быть способ получить преимущества Entity Framework для отображения O / R, но все же вручную управлять операциями внутри транзакции!