I have a County
table and have this piece of code to create new entries in the table, using ASP.NET Core 6 and EF Core 6:
public async Task<County> Add(County entity, CancellationToken cancellationToken)
{
await using (var transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken))
{
var maxGroupId = await _dbContext.County.MaxAsync(c => c.GroupId, cancellationToken);
entity.GroupId = maxGroupId + 1;
await _dbContext.County.AddAsync(entity, cancellationToken);
await transaction.CommitAsync(cancellationToken); // --> BREAKPOINT <--
}
return (await _dbContext.AddAsync(entity, cancellationToken)).Entity;
}
The idea is that every new entry should get a GroupId
that is bigger by 1 than the previous one, so after inserting NewCounty
and AnotherNewCounty
, the table should look like this:
Id | Name | GroupId
1 | County | 1
2 | NewCounty | 2
3 | AnotherNewCounty | 3
(Two entries can have the same GroupId
with another use-case, that's why it's not just an auto-incremented value by 1, but i need to ask the db for the biggest GroupId
every time i insert a new value.)
This works as expected with 1 instance of the application, but let's say I want to make sure that entries are added with the correct GroupId
even if 2 people want to add a new entry at the same time. I'm trying to simulate that use-case like this:
- I launch
instance1
of the application in debug, set the breakpoint at the CommitAsync()
(please see the code at the beginning for the breakpoint)
- I call the endpoint by inserting a new
County
with a Name
: NewCounty1
- The execution is now at the breakpoint
- I launch
instance2
of the application
- I call the endpoint by inserting a new
County
with a Name
: NewCounty2
-
NewCounty2
is inserted
- I switch to
instance1
and release the execution from the breakpoint, so NewCounty1
is inserted
The table looks like this, both of them got the same GroupId
, which is incorrect:
Id | Name | GroupId
1 | County | 1
2 | NewCounty2 | 2
3 | NewCounty1 | 2
Should look like this:
Id | Name | GroupId
1 | County | 1
2 | NewCounty2 | 2
3 | NewCounty1 | 3
I went through the different IsolationLevel
-s when calling BeginTransactionAsync()
but none of them works for me. I know that there is a description for them, but because of the lack of my knowledge, they are not really descriptive for me in terms of which database operations they translate to exactly.
In this use-case, i would like instance2
to see that instance1
already "reserved" that GroupId
in its transaction and is locking that GroupId
to prevent instance2
to assign that same GroupId
to a new County
.
Is there something wrong with my code or it's the way that I'm trying to replicate the use case is what's wrong? Or maybe both? Thank you!