Interview question
Reproduce an API concurrency conflict
Uses two isolated EF Core contexts to reproduce a real optimistic-concurrency conflict through the API boundary.
TL;DR
Uses two isolated EF Core contexts to reproduce a real optimistic-concurrency conflict through the API boundary.
Relational concurrency tokens, separate contexts, stale client versions, conflict contracts, and stored-state verification.
Practice the problem like a real interview: restate, reason, implement, and test.
Use a disposable relational database or test container. Load one record and version for two clients, save client A, then submit client B's stale version through the API. Assert 409 ProblemDetails and verify A's value remains stored.
Client A changes the note to first. Client B submits second with the old version and receives conflict; the database still contains first.
I deliberately create stale state, then send the losing request through HTTP. The final database read is as important as the 409 assertion because it proves no silent overwrite occurred.
[Fact]
public async Task Stale_update_returns_conflict_without_overwrite()
{
await using var factory = await ApiTestFactory.WithIsolatedDatabaseAsync();
var clientA = factory.CreateAuthenticatedClient("editor-a", "content.update");
var clientB = factory.CreateAuthenticatedClient("editor-b", "content.update");
var originalA = await clientA.GetFromJsonAsync<ContentDto>("/api/content/item-1");
var originalB = await clientB.GetFromJsonAsync<ContentDto>("/api/content/item-1");
var won = await clientA.PutAsJsonAsync("/api/content/item-1",
new { Text = "first", Version = originalA!.Version });
won.EnsureSuccessStatusCode();
var lost = await clientB.PutAsJsonAsync("/api/content/item-1",
new { Text = "second", Version = originalB!.Version });
Assert.Equal(HttpStatusCode.Conflict, lost.StatusCode);
var stored = await factory.ReadContentAsync("item-1");
Assert.Equal("first", stored.Text);
}
Relational behavior is the subject of the test, so an isolated real provider is required. One small container can be shared safely per test collection if each test gets its own database/schema or transaction strategy.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.