What is optimistic concurrency in EF Core, and how do you implement it?
Quick Answer
Optimistic concurrency assumes conflicts are rare: you don't lock a row while a user edits it. Instead you add a concurrency token (a rowversion/timestamp column). On SaveChanges, EF adds WHERE Id = @id AND RowVersion = @original; if another user changed the row, 0 rows update and EF throws DbUpdateConcurrencyException.
Optimistic concurrency bets that two people editing the same row at the same time is rare, so it doesn't hold a lock. I add a rowversion column as a concurrency token. When EF saves, it includes the original rowversion in the WHERE clause, so if someone else updated the row in the meantime, zero rows match, and EF raises DbUpdateConcurrencyException. Then I handle it — usually reload the current values and ask the user to review, or apply a last-write-wins or first-write-wins rule depending on what the business wants. It stops the silent lost-update where the second save overwrites the first.
- The problem it solves: the lost update — two users load the same record, both save, the second silently overwrites the first.
- Setup: a
byte[] RowVersionproperty with[Timestamp]or.IsRowVersion(). SQL Server auto-increments it on every update. - On conflict: catch
DbUpdateConcurrencyException, then decide — reload and let the user merge, take the database version, or force yours. There's no universal answer; it's a business decision. - You can also mark specific properties as concurrency tokens (
.IsConcurrencyToken()) if you don't want a rowversion column. - Contrast with pessimistic concurrency (locking with
UPDLOCK), which EF doesn't do natively and which hurts scalability.