Проблема EntityFramework 4.3 при многократном вызове SaveChanges с проверкой параллелизма - PullRequest
2 голосов
/ 06 марта 2012

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

Что-то странное в 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 изменил запись.Это совсем не смешно, потому что:

  1. Обнаружение локальных сущностей (приведений и прочего)
  2. Итерация в каждую локальную сущность и обновление ее статуса
  3. Если этоследует забыть, что проблема повторится снова.

Почему 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;
}

Это не идеальное решение.Но пока это автоматически.Нужно сделать какое-то время, пока не появится лучший ответ.

Ответы [ 2 ]

0 голосов
/ 06 апреля 2012

хорошо, небольшое обновление здесь.

  1. Было неправильно говорить, что это работало в предыдущей версии.Мы использовали 4.1 с базой данных PostgreSQL и соединителем Devart ... там это работало.Странно, я не понимаю.

  2. Обходной путь LordALMMa Также работает для меня.Но я не хочу использовать обходной путь ....

@ LordALMMa Вы уже нашли решение?

обновление 7-4-12

Я изменил свой код параллелизма.Сначала я использовал свойство DateTime следующим образом:

this.Property(t => t.lastChangedDate).IsConcurrencyToken().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

При каждом обновлении или вставке я вручную обновляю это свойство до DateTime.Now.Это было вместе с PostgreSQL и Devart Connector и работало нормально.После перехода на MS SQL я попал в проблемы.Теперь я изменил это на:

this.Property(t => t.timestamp).HasColumnName("timestamp").IsRowVersion(); 

Это работает!странно .... потому что я не делал что-то незаконное .. Этот столбец в базе данных MS имеет тип rowversion.

Так что почему-то не сгенерированная базой данных временная метка типа DateTime не работает в MS SQL с использованием ключевых слов IsConcurrencyToken и HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)

.

0 голосов
/ 06 апреля 2012

У меня также похожая проблема с EF 4.3.1.Просто сделал обновление, и он сломал мой рабочий код, основанный на 4.1.

Я не могу добавить новые объекты, которые имеют отношения, кажется.например, новый объект place со связью с объектом Country.Все мои сущности имеют свойство Concurrency:

this.Property (t => t.lastChangedDate) .HasColumnName ("lastChangedDate"). IsConcurrencyToken (). HasDatabaseGeneratedOption (DatabaseGeneratedOption.None); 100

Я обновляю это свойство самостоятельно перед CRUD, например:

virtual public void Save(T target)
{
    target.ID = GuidUtils.GenerateComb();
    target.creationDate = DateTime.Now;
    target.lastChangedDate = DateTime.Now;

    sessionManager.Set<T>().Add(target);
    sessionManager.SaveChanges();
}

Когда я сохраняю новый объект места, я получаю:

Сохранять оператор обновления, вставки или удалениязатронуло неожиданное количество строк (0).Объекты могут быть изменены или удалены с момента загрузки объектов.Обновите записи ObjectStateManager.

хммм ... я упоминал, что это был рабочий код?

Если я удаляю часть параллелизма на карте страны, она работает ... Для записи:страна, используемая для привязки объекта места, правильно хранится в базе данных до того, как был сохранен объект места.Я вижу его PK и свойство параллелизма.

Так что это похоже на проблему эталонного параллелизма в 4.3.1.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...