- Изменился объем вопроса, потому что этот другой фрагмент кода легче объяснить, но он точно такой же, как и тот, который заставил меня написать первую версию вопроса ...
Что-то странное в EF 4.3.1 Проверка параллелизма и автоматическое кэширование при вызове SaveChanges()
более одного раза.
При использовании EF 4.3 (с ошибкой типа столбца и всеми другими)тот же кусок кода работает нормально (вероятно, потому что не было никакой проверки параллелизма).При использовании 4.3.1 второй SaveChanges()
вызов падает, как показано ниже.
Вот классы:
public class Concurrent
{
public byte[] Version { get; set; }
}
public class Operation : Concurrent
{
public virtual int CustomIdName { get; set; }
public virtual C1 C1 { get; set; }
public virtual C2 C2 { get; set; }
// other properties
}
public class C1 : Concurrent
{
public virtual string CustomIdName { get; set; }
public virtual ICollection<C2> C2 { get; set; }
// other properties
}
public class C2 : Concurrent
{
public virtual string CustomIdName { get; set; }
// other properties...
}
public class Repository : DbContext, IMyCustomGenericRepository
{
public void Save() { SaveChanges(); }
public T Find<T>(object id) { return Set<T>().Find(id); }
// other methods from the interface and all...
public Repository Recreate() { return new Repository(); }
}
Некоторое кодирование ...
public void TestMethod(IMyCustomGenericRepository Repository)
{
var operation = Repository.Find<Operation>(1); // ok
operation.C1 = Repository.Find<C1>("1"); // ok
operation.C2 = Repository.Find<C2>("1"); // ok
Repository.Save(); // ok
operation = Repository.Find<Operation>(1); // ok
operation.C1 = Repository.Find<C1>("1"); // ok
operation.C2 = Repository.Find<C2>("2"); // ok
Repository.Save(); // fails
}
public void OtherTestMethod(IMyCustomGenericRepository Repository)
{
var operation = Repository.Find<Operation>(1); // ok
operation.C1 = Repository.Find<C1>("1"); // ok
operation.C2 = Repository.Find<C2>("1"); // ok
Repository.Save(); // ok
Repository = Repository.Recreate();
operation = Repository.Find<Operation>(1); // ok
operation.C1 = Repository.Find<C1>("1"); // ok
operation.C2 = Repository.Find<C2>("2"); // ok
Repository.Save(); // ok
}
MyСвободный API выглядит следующим образом:
modelBuilder.Entity<Operation>().HasKey(_o => _o.CustomIdName);
modelBuilder.Entity<Operation>().Property(_o => _o.Version).HasColumnType("timestamp").IsConcurrencyToken();
modelBuilder.Entity<C1>().HasKey(_c1 => _c1.CustomIdName);
modelBuilder.Entity<C1>().Property(_c1 => _c1.Version).HasColumnType("timestamp").IsConcurrencyToken();
modelBuilder.Entity<C2>().HasKey(_c2 => _c2.CustomIdName);
modelBuilder.Entity<C2>().Property(_c2 => _c2.Version).HasColumnType("timestamp").IsConcurrencyToken();
Единственное отличие первого метода от второго - это вызов метода Repository.Recreate()
, который возвращает целый новый репозиторий.
Обновление 2
Внутри класса Context я переопределил метод SaveChanges () следующим образом:
public override int SaveChanges()
{
// saves pending changes
var _returnValue = base.SaveChanges();
// updates EF local entities (cache)
foreach (var item in Set<Operation>().Local)
(this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);
foreach (var item in Set<C1>().Local)
(this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);
foreach (var item in Set<C2>().Local)
(this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item);
// returns the saving result
return _returnValue;
}
Этот фрагмент кода "решил" проблему, поэтому она IS как я и думал: SQL-сервер автоматически изменяет значение TimeStamp (токен параллелизма), но EF не обновляет это значение в свойстве Version по какой-то причине, которую я до сих пор не знаю.
Проблема в том, чтос неправильной версией я не могу сохранить снова просто потому, что нарушил бы проверку параллелизма из EF.
Обновление локальных сущностей (автоматически кэшируемых сущностей EF) предотвращает эту проблему НО с основной стороной-эффект: мне придется вручную обновить канунry изменил запись.Это совсем не смешно, потому что:
- Обнаружение локальных сущностей (приведений и прочего)
- Итерация в каждую локальную сущность и обновление ее статуса
- Если этоследует забыть, что проблема повторится снова.
Почему EF не обновляет автоматически колонку версий (устанавливается как IsConcurrencyToken()
), как это происходит со столбцом PK (который обновляется после вставкиесли PK - это столбец идентификаторов)?
Обновление 3 (возможное решение?)
Переопределение метода SaveChanges () из DBContext и размещение этого кода, похоже, заставляет все работать довольно хорошо:
public override int SaveChanges()
{
var entities = ChangeTracker.Entries().Where(_entry => _entry.State != System.Data.Entity.EntityState.Detached && _entry.State != System.Data.Entity.EntityState.Unchanged && _entry.State != System.Data.Entity.EntityState.Deleted).Select(_entry => _entry.Entity).ToArray();
if (!Configuration.AutoDetectChangesEnabled)
ChangeTracker.DetectChanges();
var result = base.SaveChanges();
foreach (var entity in entities)
(this as IObjectContextAdapter).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.StoreWins, entity);
return result;
}
Это не идеальное решение.Но пока это автоматически.Нужно сделать какое-то время, пока не появится лучший ответ.