✅ EF core transactions in PostgreSQL
I have a custom extension method for running operations on DbContext.
One is for running operations in transactions with a specific isolation level, and the second one is for retry logic.
The problem I just spotted is that isolation doesn't work properly, despite having enough retries and time to process transactions.
I have an entity called
WalletBalance
that contains a property Amount
.
And here is my job for testing:
I expect the amount to be 0 after all the transactions have been run. But in fact Amount
has random numbers after each run of the job.
My implementation of ExecuteInCustomStrategyAsync
and ExecuteInTransactionAsync
below9 Replies
Where
ExecuteWithRetriesAsync
is:
where DetachUpdatedEntities
is:
So, the retry policy is pretty aggressive. It has 20 retries with a max delay of 30s, which should be more than enough.
I have a feeling that I need something like SELECT FOR UPDATE
here to prevent other from reading, while I'm doing transaction
Hmm, I changed FindAsync
to FirstAsync
and it looks like the problem is solved.
As far as I know Find
caches result, so It could use entity with not actual results.
Another approach to solve this was to add await context.Entry(walletBalance).ReloadAsync();
inside transaction bodyYou're digging your own grave by playing around with
context.Entry(...)
, by using Find
and by using touching the ChangeTracker
with stuff like AcceptAllChanges
.
These are to be used only in specific cases.Unfortunately, I haven't found a better way to implement retries with transactions in EF Core with PostgreSQL.
I believe I dug my own grave when I chose EF for that project. But it is what it is, the code is running already in production and I have to fix this issue with concurrent transactions.
Also,
FirstAsync
and ReloadAsync
didn't solve my problems. I still have issues with balances.In your case, couldn't just use the
ExecuteUpdate
?Hmmm, something new. Haven't seen this method before.
Let me check
It's new in 7 or 8
This not only fixed my problem but improved performance as well.
Thanks!
Great!
In many cases, it also removes the need for transactions.
If "whether or not to execute something" (think "do I let this purchase go through or not based on the balance") is data driven, you might still need them, but when it's stuff that "has to happen" (think "view count"), it can remove the need for transactions.
Yeah, this is exactly what I thought about. If I don't need to read something before updating, then I don't need a transaction either.
I also have a place in my code where I create/delete something alongside with updating balances.
This still requires a transaction in case something fails, but I reckon I don't need it to be Serializable now