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, т.к. обработки здесь нет
    }
  }
}

И получаем:

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

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

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

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

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

SharePoint архитектор, разработчик, тренер, Microsoft MVP (Office Development). Более 15 лет опыта работы с SharePoint, Dynamics CRM, Office 365, и другими продуктами и сервисами Microsoft.

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

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

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

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

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

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

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

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

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

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

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