I had the following code that is deleting items in a table that were created more than 30 days ago (using an Entity Framework Core 6 DbContext):
var expireBefore = DateTime.Now.AddDays(-30);
db.MyTable.RemoveRange(db.MyTable.Where(t => t.CreatedDate <= expireBefore));
await db.SaveChangesAsync();
This code was executing in multiple places at the same time, and it would occasionally throw a DbUpdateConcurrencyException:
The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded
I believe this happened due to a race condition where two places both find the old entities, but only one is able to delete them.
To try and fix the issue I put the code into a transaction, I thought this would prevent the race condition, however the same exception is still occasionally thrown. Why is a transaction here not preventing this issue?
var expireBefore = DateTime.Now.AddDays(-30);
var executionStrategy = db.Database.CreateExecutionStrategy();
await executionStrategy.ExecuteAsync(async () =>
{
await using var transaction = await db.Database.BeginTransactionAsync(IsolationLevel.Serializable);
db.MyTable.RemoveRange(db.MyTable.Where(t => t.CreatedDate <= expireBefore));
await db.SaveChangesAsync();
await transaction.CommitAsync();
});