DbUpdateConcurrencyException on token update
See original GitHub issueConfirm you’ve already contributed to this project or that you sponsor it
- I confirm I’m a sponsor or a contributor
Version
4.x
Question
I’m using OpenIddict 4.1 in an ASP.NET Core 7.0 Web Api. The publicy available frontend is written in Angular and a simple password/refresh-token process is used. ASP.NET Identity is used to store user information, and an MS SQL Server database with Entity Framework to store OpenIddict data.
Normally, OpenIddict works fine with the password/refresh-token flow, but every now and then I can see a DbUpdateConcurrencyException in the log when OpenIddictEntityFrameworkCoreTokenStore.UpdateAsync()
is called by OpenIddict. I traced the problem down: the exception occurs when a user opens a browser with multiple previously opened tabs of my web application. Then multiple parallel requests could be sent to the /connect/token
endpoint to get a new refresh token. This could happen in exact the same millisecond (I’ve seen this when using Firefox for myself). And then the following exception gets logged (logger: Microsoft.EntityFrameworkCore.Update
):
Microsoft.EntityFrameworkCore.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. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Update.Internal.SqlServerModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at OpenIddict.EntityFrameworkCore.OpenIddictEntityFrameworkCoreTokenStore`5.UpdateAsync(TToken token, CancellationToken cancellationToken)
That’s all information I get in the log, there is no further stacktrace from where OpenIddictEntityFrameworkCoreTokenStore.UpdateAsync()
has been called. I tried to cover the code in the /connect/token
endpoint with a user-based lock, but this has not helped.
I noticed the problems began when I switched from OpenIddict 3 to OpenIddict 4 and from .NET 6 to .NET 7 (the problem didn’t occur before). Any help is highly appreciated 👍
Issue Analytics
- State:
- Created 7 months ago
- Comments:12 (5 by maintainers)
Top GitHub Comments
Looking at the EF sourcecode and internal EF logging, this seems to be the cause of the problem. So, OpenIddict is handling the exception as expected from the sourcecode. I’ll configure EF to ignore the DbUpdateConcurrencyException in logging. Thanks for your help.
A guess is that it is coming from the logger from inside the DbContext. It is handled in the code, but the log still appears since it is logged prior to that from Ef.