RowVersion and Timestamps in EF Core: Practical Guide for SQL Server

September 22, 2025 · 8 min

You add a RowVersion property to your entity, mark it with [Timestamp], and think you’re done with concurrency control. Then production hits: concurrency exceptions aren’t caught properly, API clients can’t handle the binary data, and you’re not sure if your timestamps are working.

SQL Server’s rowversion is powerful but has specific quirks that can trip up EF Core developers. Here’s how to implement it correctly.

Understanding SQL Server RowVersion

SQL Server’s rowversion is not a timestamp despite the old name. It’s an 8-byte binary value that automatically increments whenever any column in the row changes:

CREATE TABLE [Products] (
    [Id] int IDENTITY(1,1) NOT NULL,
    [Name] nvarchar(100) NOT NULL,
    [Price] decimal(18,2) NOT NULL,
    [RowVersion] rowversion NOT NULL,
    CONSTRAINT [PK_Products] PRIMARY KEY ([Id])
);

Every time you UPDATE this row, SQL Server automatically changes the RowVersion value. You never set it manually - the database engine handles everything.

EF Core RowVersion Implementation

The correct way to implement rowversion in EF Core:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    [Timestamp] // This maps to SQL Server rowversion
    public byte[] RowVersion { get; set; }
}

The [Timestamp] attribute tells EF Core this is a concurrency token that’s automatically generated by the database. EF Core …

Read more

EF Core Concurrency Tokens: Preventing Lost Updates in SQL Server

September 21, 2025 · 7 min

Two users edit the same customer record simultaneously. User A updates the phone number, User B changes the address. User A saves first, then User B saves. Result: the phone number update disappears.

This is the classic lost update problem, and it happens more often than you think in production web applications. EF Core concurrency tokens solve this elegantly without the complexity of database locks.

The Lost Update Problem

Without concurrency control, this scenario plays out daily in production:

// User A loads customer
var customer = await context.Customers.FindAsync(customerId);
customer.Phone = "555-0123";

// User B loads the same customer (before A saves)
var customer2 = await context.Customers.FindAsync(customerId);
customer2.Address = "123 New Street";

// User A saves first
await context.SaveChangesAsync(); // Phone updated

// User B saves second  
await context.SaveChangesAsync(); // Address updated, but phone reverted!

User B’s save overwrote User A’s phone number change because EF Core generated an UPDATE statement based on the original values User B loaded. The phone number silently reverted to its original value.

Concurrency Tokens to the Rescue

Concurrency tokens enable optimistic concurrency control. EF Core includes the token value in UPDATE and DELETE statements, ensuring the operation only succeeds if no one else modified the record:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } …

Read more