Ответ
Оптимистическая блокировка (Optimistic Concurrency Control) — это стратегия управления параллельным доступом к данным, которая предполагает, что конфликты между транзакциями редки. Вместо блокировки строк на время чтения, она проверяет, не изменились ли данные с момента их чтения, непосредственно перед записью.
Принцип работы:
- Чтение: Клиент читает запись, получая ее текущее состояние и версию (например,
Version= 5). - Модификация: Клиент изменяет данные локально.
- Запись: При отправке обновления клиент включает прочитанную версию (
Version = 5). - Проверка: Система обновляет запись только если ее текущая версия в БД все еще равна 5, и одновременно увеличивает версию (до 6). Если версия изменилась (другой клиент уже обновил запись), операция завершается ошибкой (
DbUpdateConcurrencyExceptionв EF Core).
Реализация:
1. Добавление столбца версии в сущность
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Свойство для оптимистической блокировки
[Timestamp] // Для SQL Server (тип rowversion)
public byte[] RowVersion { get; set; }
// ИЛИ для других БД (int версия)
// public int Version { get; set; }
}
2. Обработка в коде с использованием EF Core
try
{
var product = await _context.Products.FindAsync(productId);
product.Price = newPrice; // Изменяем данные
// EF Core автоматически проверит RowVersion/Version при SaveChanges
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
// Конфликт! Данные были изменены кем-то другим.
foreach (var entry in ex.Entries)
{
var databaseValues = await entry.GetDatabaseValuesAsync();
if (databaseValues == null)
{
Console.WriteLine("Запись была удалена другим пользователем.");
}
else
{
// Получаем актуальные значения из БД
var currentValues = entry.CurrentValues;
var dbProduct = (Product)databaseValues.ToObject();
// Стратегия разрешения конфликта:
// 1. Перезаписать клиентскими данными (Client Wins) - entry.OriginalValues.SetValues(databaseValues); затем повторить.
// 2. Принять серверные данные (Store Wins) - entry.CurrentValues.SetValues(databaseValues);
// 3. Объединить изменения вручную.
Console.WriteLine($"Конфликт! Текущая цена в БД: {dbProduct.Price}");
}
}
}
3. Ручная проверка через WHERE-условие (SQL)
UPDATE Products
SET Price = @NewPrice, Version = Version + 1
WHERE Id = @ProductId AND Version = @OriginalVersion;
-- Проверяем количество затронутых строк. Если 0 — значит, версия изменилась.
Когда использовать:
- Оптимистическая: Для сценариев с низкой вероятностью конфликтов (редактирование профилей, каталогов товаров). Высокая производительность чтения.
- Пессимистическая: Для сценариев с высокой конкуренцией (бухгалтерские операции, бронирование последнего билета). Использует блокировки
SELECT ... FOR UPDATE.