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

EntityFramework

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

Подготовка

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

Диаграмма базы данных

Диаграмма базы данных

Модель данных EntityFramework

Модель данных EntityFramework

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

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

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

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

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

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

exec sp_executesql N'update [dbo].[Article]
set [Note] = @0
where ([Id] = @1)
select [TimeStamp]
from [dbo].[Article]
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.

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

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

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

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

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

class Program
{
  static void Main(string[] args)
  {
    // Наш контекст
    using(var ctx = new ZhukPointEntities())
    {
      // Берем заранее подготовленный объект
      var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
      // Меняем свойство
      article.Note = DateTime.Now.ToShortDateString();
      using(var _ctx = new ZhukPointEntities())
      {
        var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
        _article.Note = DateTime.Now.ToShortDateString();
        _ctx.SaveChanges();
      }
      // Сохраняем изменения
      ctx.SaveChanges();
    }
  }
}

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

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

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

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

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

class Program
{
  static void Main(string[] args)
  {
    // Наш контекст
    using (var ctx = new ZhukPointEntities())
    {
      // Берем заранее подготовленный объект
      var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
      // Меняем свойство
      article.Note = DateTime.Now.ToShortDateString();
      using (var _ctx = new ZhukPointEntities())
      {
        var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
        _article.Note = DateTime.Now.ToShortDateString();
        _ctx.SaveChanges();
      }
      try
      {
        // Сохраняем изменения
        ctx.SaveChanges();
      }
      catch (OptimisticConcurrencyException ex)
      {
        // Обновляем объект с приоритетом клиентской стороне
        ctx.Refresh(RefreshMode.ClientWins, article);
        // Сохраняем изменения
        ctx.SaveChanges();
      }
    }
  }
}

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

static void Main(string[] args)
{
  // Наш контекст
  using (var ctx = new ZhukPointEntities())
  {
    // Берем заранее подготовленный объект
    var article = ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
    // Меняем свойство
    article.Note = DateTime.Now.ToString();
    using (var _ctx = new ZhukPointEntities())
    {
      var _article = _ctx.Article.First(a => a.Id == new Guid("27713880-e3e3-4073-8f41-71fb511501e9"));
      _article.Note = DateTime.Now.ToString();
      _ctx.SaveChanges();
    }
    try
    {
      // Сохраняем изменения
      ctx.SaveChanges();
    }
    catch (OptimisticConcurrencyException ex)
    {
      // Перебираем объекты, сохранить которые не удалось
      foreach (var stateEntry in ex.StateEntries)
      {
        // Перебираем измененный свойства у объекта
        var properties = stateEntry.GetModifiedProperties();
        foreach (var property in properties)
        {
          // Текущее значение на клиенте
          var _new = stateEntry.CurrentValues[property];
          // Значение на сервере
          var _old = stateEntry.OriginalValues[property];
          // Выводим информацию об измененных данных
          Console.WriteLine(property + "\t" + _old + "\t" + _new);
          // Дальше ваша логика, для делегирования принятия решения клиенту (или логике)
        }
      }
      // Здесь Exception, т.к. обработки здесь нет
    }
  }
}

И получаем:

Информация о конфликте при сохранении данных

Информация о конфликте при сохранении данных

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

Виталий Жуков

Виталий Жуков

Техлид, Архитектор, Разработчик, Microsoft MVP. Более 20 лет опыта в области системной интеграции и разработки программного обеспечения. Специализируюсь на проектировании и внедрении масштабируемых высокопроизводительных программных решений в различных отраслях.

Смотрите также

Развертывание списков и библиотек с помощью SPFx-решений

Развертывание списков и библиотек с помощью SPFx-решений

SharePoint. Drag-and-Drop Загрузчик файлов

SharePoint. Drag-and-Drop Загрузчик файлов

CSOM. Загрузка файлов

CSOM. Загрузка файлов

SharePoint List REST API. Часть 2

SharePoint List REST API. Часть 2

SharePoint Framework. Создание веб-части на Angular

SharePoint Framework. Создание веб-части на Angular

SharePoint List REST API. Часть 1

SharePoint List REST API. Часть 1

Презентация с доклада о SharePoint Framework

Презентация с доклада о SharePoint Framework

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Расширяем REST API

SharePoint. Расширяем REST API

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Ribbon API. Использование ToggleButton

SharePoint Ribbon API. Использование ToggleButton

SharePoint 2013. How To: настройка входящей почты для разработчиков

SharePoint 2013. How To: настройка входящей почты для разработчиков

Мифы и правда о Linq to SharePoint

Мифы и правда о Linq to SharePoint

5 особенностей SPSiteDataQuery

5 особенностей SPSiteDataQuery

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 1

SharePoint 2013. Введение в SharePoint App. Часть 1

Превью для веб-части в SharePoint 2010/2013

Превью для веб-части в SharePoint 2010/2013

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Тип поля Geolocation

SharePoint 2013. Тип поля Geolocation

Создание типа поля в SharePoint

Создание типа поля в SharePoint

SharePoint 2010. Длительные операции с обновляемым статусом

SharePoint 2010. Длительные операции с обновляемым статусом

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Версионность

Linq to SharePoint. Версионность

SharePoint. Получение URL-адреса иконки для документа

SharePoint. Получение URL-адреса иконки для документа

SharePoint 2010. PostBack для Fluent Ribbon API

SharePoint 2010. PostBack для Fluent Ribbon API

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Формирование данных для ProcessBatchData

Linq to SharePoint. Формирование данных для ProcessBatchData

Linq to SharePoint. Сравнение производительности с Camlex.NET

Linq to SharePoint. Сравнение производительности с Camlex.NET

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 2

Linq to SharePoint. Особенности. Часть 2

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. Настройка входящей почты для кастомного списка

SharePoint 2010. Настройка входящей почты для кастомного списка

Linq to Sharepoint. Особенности

Linq to Sharepoint. Особенности