SF

PostgreSQL · SignalR · Testcontainers

Optimistic concurrency: the conflict you never see

Two users, one record, no silent overwrite. xmin, 409 and real-time presence.

409
instead of an overwrite
1
column added: none
2
concurrent writes tested in CI

The problem

On a time-tracking platform, several managers edit the same records at once — at month end, on the same teams. Entity Framework's default behaviour is last-write-wins: the second save overwrites the first with no warning, no trace, no error. Nobody notices until a customer reports missing hours, and by then there is no way to reconstruct what was lost.

User Axmin 1042
User Bxmin 1042

db → "Refonte du portail client" · xmin 1042

The approach

PostgreSQL already maintains a system version counter on every row, xmin, incremented on each write. Rather than adding an application-level version column to maintain, I declared it as the EF Core concurrency token on the affected aggregates. The client reads xmin alongside the data and sends it back on save; if the value moved in the meantime, EF Core raises a DbUpdateConcurrencyException and the API layer translates it into HTTP 409, without ever writing. On top of that, real-time editing presence over SignalR shows a banner as soon as another user opens the same field — the collision is surfaced before it happens, not merely rejected afterwards.

Code excerptTimesheetConfiguration.cs
// PostgreSQL already versions every row through the system column xmin.
// Using it as the concurrency token means no extra column to add, migrate
// or keep in sync — the database does the bookkeeping we would otherwise
// have to write and test ourselves.
public void Configure(EntityTypeBuilder<Timesheet> builder)
{
    builder.Property<uint>("xmin")
           .HasColumnType("xid")
           .ValueGeneratedOnAddOrUpdate()
           .IsConcurrencyToken();
}

// The API layer turns the EF exception into a contract the client can act on:
// the caller learns the row moved, and gets both values to show a real diff.
[HttpPut("{id:guid}")]
public async Task<IActionResult> Update(Guid id, UpdateTimesheetRequest request)
{
    try
    {
        await _timesheets.UpdateAsync(id, request, request.RowVersion);
        return Ok(ApiResponse.Success());
    }
    catch (DbUpdateConcurrencyException ex)
    {
        var current = ex.Entries.Single().GetDatabaseValues();

        return Conflict(ApiResponse.Conflict(
            expected: request.RowVersion,
            actual: current?["xmin"]));
    }
}

The result

Silent overwrites are gone from the model: they are now structurally impossible on the covered aggregates. The behaviour is verified in CI by PostgreSQL integration tests running under Docker and Testcontainers, replaying two genuinely concurrent writes and asserting both the 409 and multi-tenant isolation — not mocks, a real database. The demonstration higher up this site reproduces the mechanism faithfully.

ASP.NET Core Identity · JWT · SecurityThe session you thought was closedCompliance · CQRS · MapperlyGetting off MediatR and AutoMapper, in production