TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #2309 2.83K
День 1911. #ЗаметкиНаПолях
Реализуем Пессимистическую Блокировку в EF Core. Начало

Иногда, особенно в сценариях с высоким трафиком, вам абсолютно необходимо убедиться, что только один процесс может одновременно изменять какую-то часть данных.

Стандартный пример - система продажи билетов на популярное событие. При большом трафике билеты на одно место могут быть куплены одновременно несколькими людьми. В EF Core нет прямого механизма пессимистичной блокировки. Оптимистическая блокировка (с использованием версий) может работать, но в сценариях с высоким уровнем конфликтов это может привести к множеству повторных попыток.

Вот упрощённый фрагмент кода, иллюстрирующий проблему с билетами:
public async Task Checkout(ShopCart cart)
{
await using var trans = await
context.BeginTransactionAsync();
…
var order = new Order();
foreach (CartItem item in cart.Items)
{
// Проверяем доступность билета
// Что, если два запроса проверят его в одно время?
var ticket = await ticketRepo.GetAsync(
item.TicketId);

ticket.UpdateQuantity(item.Quantity);
order.Add(ticket, item.Quantity, item.Price);
}
orderRepo.Insert(order);

await context.SaveChangesAsync();
await trans.CommitAsync();
…
}

Пример выше надуманный, но суть понятна. Во время оформления заказа мы проверяем доступное количество для каждого типа билетов. Что, если мы получим одновременные запросы на покупку одного и того же билета? В худшем случае мы продадим билеты несколько раз. Одновременные запросы могут увидеть доступность билета и оба завершить оформление заказа.

Поскольку EF Core не предлагает пессимистическую блокировку напрямую, немного углубимся в старый добрый SQL. Заменим вызов GetAsync для получения билета на GetWithLockAsync:
public async Task<Ticket> GetWithLockAsync(Guid id)
{
return await context
.Tickets
.FromSql(
$@"SELECT id, event_id, price, quantity
FROM tickets WHERE id = {id}
FOR UPDATE NOWAIT")
.SingleAsync();
}

FOR UPDATE NOWAIT - суть пессимистической блокировки в PostgreSQL (и Oracle). Он сообщает базе данных: «Захвати эту строку, заблокируй её для меня, и, если она уже заблокирована, прямо сейчас выдай ошибку».

Вызов GetWithLockAsync нужно обернуть в try-catch, чтобы корректно обрабатывать сбои блокировки, либо повторяя попытку, либо уведомляя пользователя.

Поскольку в EF Core нет встроенного способа добавления подсказок к запросам, приходится писать чистый SQL-запрос. Мы можем использовать оператор SELECT FOR UPDATE, чтобы получить блокировку на уровне выбранных строк. Любые конкурирующие транзакции будут заблокированы до тех пор, пока текущая транзакция не снимет блокировку. Это очень простой способ реализовать пессимистическую блокировку.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/a-clever-way-to-implement-pessimistic-locking-in-ef-core
  • 👍 40
More from @netdeveloperdiary
  1. Oct 8, 2026День 2808. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Начало В ИТ есть се…
  2. Oct 7, 2026День 2807. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Окончание Нач…
  3. Oct 6, 2026🦈 Открытое собеседование на Middle C# | 6 октября, 19:00 МСК Приглашаем на открытое собес…
  4. Oct 6, 2026День 2806. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Начало Больши…
  5. Oct 5, 2026День 2805. #ЧтоНовенького #NET11 Аргументы в Выражениях Коллекций в C#15 В C#15 реализован…
  6. Oct 4, 2026День 2804. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как тех…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →