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, т.к. обработки здесь нет
}
}
}
И получаем:
Осталось только начать применять.