Похоже, вам нужен пессимистичный параллельный подход для управления вашей задачей.К сожалению, это не поддерживается в Entity Framework Core.
В качестве альтернативы, вы можете использовать статический ConcurrentDictionary или реализовать свой собственный ConcurrentHashSet, чтобы обезопасить себя от нескольких запросов и избежать того, чтобы другое назначение можно было забронировать после шага 2, но до шага 3.
О том, что слот календаря, извлеченный в 1, был удален перед проблемой шага 3, я думаю, что наличие отношения внешнего ключа между назначением и слотом для проверки целостности базы данных в SaveChanges или наличие ConcurrentDictionary / ConcurrentHashSet Public и проверка егоот других действий (удалить слоты и т. д.) перед их выполнением, есть хорошие варианты для его решения.
static ConcurrentDictionary<int, object> operations = new ConcurrentDictionary<int, object>();
public async Task<IActionResult> AControllerAction()
{
int? calendarSlotId = 1; //await dbContext.Slots.FirstOrDefaultAsync(..., cancellationToken))?.Id;
try
{
if (calendarSlotId != null && operations.TryAdd(calendarSlotId.Value, null))
{
bool appointmentsAreOverlapping = false; //await dbContext.Slots.Where(...).AnyAsync(cancellationToken);
if (!appointmentsAreOverlapping)
{
//dbContext.Appointments.Add(...);
//await dbContext.SaveChangesAsync(cancellationToken);
return ...; //All done!
}
return ...; //Appointments are overlapping
}
return ...; //There is no slot or slot is being used
}
catch (Exception ex)
{
return ...; //ex exception (DB exceptions, etc)
}
finally
{
if (calendarSlotId != null)
{
operations.TryRemove(calendarSlotId.Value, out object obj);
}
}
}