In this lesson, you will learn how to prevent "lost updates" in multi-user applications by implementing optimistic concurrency using RowVersion tokens.
The Concurrency Problem
Imagine two users open the same product record simultaneously. User A changes the price to 12. If they both hit "Save" at the same time, the last person to save overwrites the other, and the first person’s changes vanish without warning. Optimistic concurrency assumes collisions are rare but provides a safety net to ensure we never overwrite data that has changed since we last read it.
Implementing RowVersion
To handle this, we use a byte[] property marked as a [Timestamp]. In SQL Server, this maps to a rowversion column, which the database automatically increments every time the row is updated.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
How It Works
When you query a record, EF Core tracks the RowVersion value. When you call SaveChanges(), EF Core includes this token in the WHERE clause of the generated UPDATE statement:
UPDATE Products SET Price = @p1 WHERE Id = @id AND RowVersion = @originalVersion
If another user modified the row between your read and your write, the RowVersion in the database will have changed. Your update statement will fail to match any rows, and EF Core will throw a DbUpdateConcurrencyException.
Handling the Collision
When this exception occurs, your application should catch it, inform the user that the record was modified by someone else, and decide whether to reload the new data or force the overwrite. This pattern ensures that your database remains the "source of truth" and prevents accidental data loss during high-traffic scenarios.
Key Takeaway: By marking a byte-array property with the [Timestamp] attribute, you force EF Core to include it in update commands, ensuring the database rejects any save attempts based on stale, outdated information.