EntityFrameWork. Оптимистические блокировки

Entity Framework

EntityFramework позволяет работать с оптимистическими блокировками, возникающими при работе с СУБД, и обрабатывать возникающие исключения. В посте я покажу небольшой пример того, как можно применить этот функционал.

Подготовка

Сделаем маленькую базку и консольное приложение с моделью данных. У меня получилось примерно так:

Диаграмма базы данных Модель данных EF

Связь многие-ко-многим EF "проглотила", но если в таблице связей добавить ещё одно поле, то EF не станет вмешиваться в структуру данных.

Контрольная группа

Для начала попробуем проделать операцию модификации данных без включения оптимистических блокировок и посмотрим "внутрь".

Запустим нашу консоль со следующим кодом:

  1. class Program
  2. {
  3.   static void Main(string[] args)
  4.   {
  5.     // Наш контекст
  6.     using(var ctx = new ZhukPointEntities())
  7.     {
  8.       // Берем заранее подготовленный объект
  9.       var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  10.       // Меняем свойство
  11.       article.Note = DateTime.Today.ToShortDateString();
  12.       // Сохраняем изменения
  13.       ctx.SaveChanges();
  14.     }
  15.   }
  16. }

В профайлере смотрим и видим:

  1. exec sp_executesql N'update [dbo].[Article]
  2. set [Note] = @0
  3. where ([Id] = @1)
  4. select [TimeStamp]
  5. from [dbo].[Article]
  6. where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(1000),@1 uniqueidentifier',@0=N'20.11.2010',@1='27713880-E3E3-4073-8F41-71FB511501E9'

Понеслась

Теперь включаем фичу. Для этого в свойствах поля TimeStamp объекта Article переключаем Concurrency Mode в Fixed.

Включение оптимистичной блокировки

Повторяем процедуру, смотрим профайлер:

  1. exec sp_executesql N'update [dbo].[Article]
  2. set [Note] = @0
  3. where (([Id] = @1) and ([TimeStamp] = @2))
  4. select [TimeStamp]
  5. from [dbo].[Article]
  6. where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(1000),@1 uniqueidentifier,
  7. @2 binary(8)',
  8. @0=N'20.11.2010',@1='27713880-E3E3-4073-8F41-71FB511501E9',
  9. @2=0x00000000000007D5

За что боролись, на то и напоролись. Теперь сделаем вид, что данные были изменены после считывания из таблицы и теперь мы их пытаемся сохранить. Код будет примерно такой:

  1. class Program
  2. {
  3.   static void Main(string[] args)
  4.   {
  5.     // Наш контекст
  6.     using(var ctx = new ZhukPointEntities())
  7.     {
  8.       // Берем заранее подготовленный объект
  9.       var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  10.       // Меняем свойство
  11.       article.Note = DateTime.Now.ToShortDateString();
  12.       using(var _ctx = new ZhukPointEntities())
  13.       {
  14.         var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  15.         _article.Note = DateTime.Now.ToShortDateString();
  16.         _ctx.SaveChanges();
  17.       }
  18.       // Сохраняем изменения
  19.       ctx.SaveChanges();
  20.     }
  21.   }
  22. }

Запускаем, смотрим и видим:

Исключение, полученное после включения оптимистической блокировки

Обрабатываем исключение

Сначала решим задачу "сохранить любой ценой". Изменим код:

  1. class Program
  2. {
  3.   static void Main(string[] args)
  4.   {
  5.     // Наш контекст
  6.     using (var ctx = new ZhukPointEntities())
  7.     {
  8.       // Берем заранее подготовленный объект
  9.       var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  10.       // Меняем свойство
  11.       article.Note = DateTime.Now.ToShortDateString();
  12.       using (var _ctx = new ZhukPointEntities())
  13.       {
  14.         var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  15.         _article.Note = DateTime.Now.ToShortDateString();
  16.         _ctx.SaveChanges();
  17.       }
  18.       try
  19.       {
  20.         // Сохраняем изменения
  21.         ctx.SaveChanges();
  22.       }
  23.       catch (OptimisticConcurrencyException ex)
  24.       {
  25.         // Обновляем объект с приоритетом клиентской стороне
  26.         ctx.Refresh(RefreshMode.ClientWins, article);
  27.         // Сохраняем изменения
  28.         ctx.SaveChanges();
  29.       }
  30.     }
  31.   }
  32. }

На стороне SQL Server'а происходит загрузка объекта и очередная попытка сохранения. Все работает. Теперь попробуем вывести информацию о свойствах элемента, из-за которых и произошло исключение. Теперь код будет таким:

  1. static void Main(string[] args)
  2. {
  3.   // Наш контекст
  4.   using (var ctx = new ZhukPointEntities())
  5.   {
  6.     // Берем заранее подготовленный объект
  7.     var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  8.     // Меняем свойство
  9.     article.Note = DateTime.Now.ToString();
  10.     using (var _ctx = new ZhukPointEntities())
  11.     {
  12.       var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
  13.       _article.Note = DateTime.Now.ToString();
  14.       _ctx.SaveChanges();
  15.     }
  16.     try
  17.     {
  18.       // Сохраняем изменения
  19.       ctx.SaveChanges();
  20.     }
  21.     catch (OptimisticConcurrencyException ex)
  22.     {
  23.       // Перебираем объекты, сохранить которые не удалось
  24.       foreach (var stateEntry in ex.StateEntries)
  25.       {
  26.         // Перебираем измененный свойства у объекта
  27.         var properties = stateEntry.GetModifiedProperties();
  28.         foreach (var property in properties)
  29.         {
  30.           // Текущее значение на клиенте
  31.           var _new = stateEntry.CurrentValues[property];
  32.           // Значение на сервере
  33.           var _old = stateEntry.OriginalValues[property];
  34.           // Выводим информацию об измененных данных
  35.           Console.WriteLine(property + "\t" + _old + "\t" + _new);
  36.           // Дальша ваша логика, для делегирования принятия решения клиенту (или логике)
  37.         }
  38.       }
  39.       // Здесь Exception, т.к. обработки здесь нет
  40.     }
  41.   }
  42. }

И получаем:

Осталось только начать применять


Поделиться

Коментарии