To visualize - naively - if I would like to complete such an operation, I would probably use the code below.
Assume that dbContext
and dbContext.Entries
are of type DbContext
(PostgreSQL) and DbSet<Entry>
respectively.
var entry = await dbContext.Entries.FindAsync(key);
dbContext.Entries.Remove(entry);
await dbContext.SaveChangesAsync();
return entry;
However, if this code is executed concurrently by two or more processes, I think it is possible that it may not be entirely isolated (correct me if I am wrong). For example, both processes receive the same entry from the first line first, then remove it. Although the entry will still be removed properly, both processes receive the entry, which violates the isolation rule.
I wonder if it is possible to handle this situation using EF Core so that each transaction is totally isolated from others?
One way I found from my research is to encase that piece of code with an explicit transaction statement (docs) and change the isolation level if needed. However, from the docs of DbSet.FindAsync()
, the query to the database will not be made if "the given primary key values is being tracked by the context."
And I am not sure whether the DbSet.FindAsync()
operation will be considered sequentially in the explicitly stated transaction like the modification operations (e.g. DbSet.Remove()
, DbSet.Add()
) or not.
Please correct me if any of my assumptions are wrong and thanks in advance!
Edit: Sorry to mention; I use PostgreSQL with EF Core.
CodePudding user response:
If entity framework tries to modify or delete a record that is missing or had been recently updated, it will throw a DbUpdateConcurrencyException
.
DbUpdateConcurrencyException
Exception thrown by DbContext when it was expected that SaveChanges for an entity would result in a database update but in fact no rows in the database were affected. This usually indicates that the database has been concurrently updated such that a concurrency token that was expected to match did not actually match
Hence you could wrap your code in a try catch and catch that exception.
try
{
var entry = await dbContext.Entries.FindAsync(key);
dbContext.Entries.Remove(entry);
await dbContext.SaveChangesAsync();
return entry;
}
catch(DbUpdateConcurrencyException e)
{
// do something with e
}
catch(Exception e)
{
// if something else happens, like null reference
}