Ваша проблема в том, что подобные операции ...
_context.Sessions.FirstOrDefault(s => s.Id == sessionId).PlayerCount += 1;
_context.SaveChanges();
... не атомарны. С FirstOrDefault
вы получаете сеанс (который вы разыменовываете без проверки null
, так что First
будет лучшим вариантом здесь, так как вы получите лучшее сообщение об ошибке). Затем вы сохраняете изменения в следующем шаге. Между этими шагами другой параллельный поток мог измениться и уже сохранить новое значение для PlayerCount
.
Существует несколько способов решения этой проблемы, и большинство из них потребуют некоторых изменений на уровне БД.
Один из способов решения этой проблемы - написать хранимую процедуру, которая может выполнять обновление атомарно. Вам понадобится оператор UPDATE
, похожий на этот:
UPDATE Sessions
SET PlayerCount = PlayerCount + 1
FROM Sessions
WHERE Id = @SessionId
Если вы не хотите использовать хранимые процедуры, вы можете отправить этот SQL напрямую в БД.
Одна из проблем этого решения заключается в том, что позже вы также выполните этот шаг:
if (thisSession.Size == thisSession.PlayerCount)
{
thisSession.Status = SessionStatus.Active;
_context.SaveChanges(); // this can be trimmed and added at the previous scope
}
Вы должны будете интегрировать это в ваш оператор UPDATE
, чтобы операция оставалась атомарной. Если вы хотите добавить дополнительные шаги, все может стать сложнее.
Другой способ - использовать оптимистичный параллелизм, встроенный в ядро EF. В двух словах, это означает, что при сохранении данных ef сначала проверит, соответствует ли строка назначения той же версии по сравнению с моментом, когда вы ее получили.
Для этого вашему сеансу нужен столбец, который будет содержать счетчик версий. Для SQL Server это будет столбец типа rowversion
. Если у вас есть этот столбец, EF может использовать магию оптимистичного параллелизма. EF выбросит DbUpdateConcurrencyException
, с которым вам придется справиться. Существуют разные способы устранения ошибки. Один простой способ - повторить всю операцию, пока она не сработает.