Как обрабатывать конфликты параллелизма в Entity Framework

Обработка параллелизма может использоваться для поддержания целостности и согласованности данных, когда несколько пользователей одновременно обращаются к одному и тому же ресурсу. Нарушения параллелизма могут возникать, когда у вас есть взаимозависимые транзакции, то есть транзакции, которые зависят друг от друга и пытаются получить доступ к одному и тому же ресурсу.

Обработка конфликтов параллелизма в Entity Framework

Давайте теперь разберемся, как каждая из этих стратегий работает в Entity Framework. При пессимистическом параллелизме, когда обновляется конкретная запись, все другие одновременные обновления той же записи будут приостановлены, пока текущая операция не будет завершена, и управление не будет возвращено, чтобы другие параллельные операции могли продолжаться. В режиме оптимистичного параллелизма «выигрывает» последняя сохраненная запись. В этом режиме предполагается, что конфликты ресурсов из-за одновременного доступа к общему ресурсу маловероятны, но не невозможны.

Между прочим, Entity Framework по умолчанию обеспечивает поддержку оптимистичного параллелизма. Entity Framework не поддерживает пессимистичный параллелизм из коробки. Давайте теперь поймем, как Entity Framework разрешает конфликты параллелизма при работе в оптимистическом параллелизме (режим по умолчанию).

При работе с оптимистичным режимом обработки параллелизма вы, как правило, хотите сохранить данные в своей базе данных, предполагая, что данные не изменились с момента загрузки в память. Обратите внимание, что при попытке сохранить изменения в базе данных с помощью метода SaveChanges в экземпляре контекста данных будет выброшено исключение DbUpdateConcurrencyException. Давайте теперь разберемся, как это исправить.

Чтобы проверить нарушение параллелизма, вы можете включить поле в свой класс сущности и пометить его с помощью атрибута Timestamp. См. Приведенный ниже класс сущности.

public class Author

   {

       public Int32 Id { get; set; }

       public string FirstName { get; set; }

       public string LastName { get; set; }

       public string Address { get; set; }

       [Timestamp]

       public byte[] RowVersion { get; set; }

   }

Теперь Entity Framework поддерживает два режима параллелизма: None и Fixed. В то время как первое подразумевает, что при обновлении объекта проверки параллелизма не будут выполняться, второе подразумевает, что исходное значение свойства будет учитываться при выполнении предложений WHERE в то время, когда выполняются обновления или удаления данных. Если у вас есть свойство, помеченное с помощью отметки времени, режим параллелизма считается фиксированным, что, в свою очередь, означает, что исходное значение свойства будет учитываться в предложении WHERE любых обновлений или удалений данных для этого конкретного объекта.

Чтобы разрешить конфликты оптимистичного параллелизма, вы можете воспользоваться методом Reload, чтобы обновить текущие значения в вашей сущности, находящейся в памяти, с последними значениями в базе данных. После перезагрузки с обновленными данными вы можете снова попытаться сохранить свою сущность в базе данных. В следующем фрагменте кода показано, как этого можно достичь.

using (var dbContext = new IDBDataContext())

{

     Author author = dbContext.Authors.Find(12);

     author.Address = "Hyderabad, Telengana, INDIA";

       try

         {

             dbContext.SaveChanges();

         }

         catch (DbUpdateConcurrencyException ex)

         {

             ex.Entries.Single().Reload();

             dbContext.SaveChanges();

         }

}

Обратите внимание, что вы можете использовать метод Entries в экземпляре DbUpdateConcurrencyException, чтобы получить список экземпляров DbEntityEntry, соответствующих сущностям, которые не могли быть обновлены при вызове метода SaveChanges для сохранения сущностей в базе данных.

Подход, который мы только что обсудили, часто называют «сохраненные выигрыши» или «выигрыши базы данных», поскольку данные, содержащиеся в сущности, перезаписываются данными, доступными в базе данных. Вы также можете использовать другой подход, называемый «клиент выигрывает». В этой стратегии данные из базы данных извлекаются для заполнения объекта. По сути, данные, полученные из базовой базы данных, устанавливаются как исходные значения для объекта. В следующем фрагменте кода показано, как этого можно достичь.

try

{

     dbContext.SaveChanges();

}

catch (DbUpdateConcurrencyException ex)

{

   var data = ex.Entries.Single();

   data.OriginalValues.SetValues(data.GetDatabaseValues());

}

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

catch (DbUpdateConcurrencyException ex)

{

   var entity = ex.Entries.Single().GetDatabaseValues();

   if (entity == null)

   {

         Console.WriteLine("The entity being updated is already deleted by another user...");

   }

   else

   {

         Console.WriteLine("The entity being updated has already been updated by another user...");

   }

}

Если в вашей таблице базы данных нет столбца с меткой времени или версии строки, вы можете воспользоваться атрибутом ConcurrencyCheck для обнаружения конфликтов параллелизма при использовании Entity Framework. Вот как используется это свойство.

[Table("Authors"]

public class Author

{

   public Author() {}

   [Key]

   public int Id { get; set; }

   [ConcurrencyCheck]

   public string FirstName { get; set; }

   public string LastName { get; set; }

   public string Address { get; set; }

}

При этом SQL Server будет автоматически включать AuthorName при выполнении операторов обновления или удаления в базе данных.