如何处理 IdentityUser 更新并发失败?

How to deal with concurrency failure on IdentityUser update?

我有一种方法可以在我们的应用程序中更新用户声明。

我以管理员用户身份登录,可以编辑其他用户。

我正在尝试删除一位用户的现有声明并分配新声明。

当使用 UserManger 删除声明时,结果是 ConcurrencyFailure。 (有时它可以工作,但大多数时候它 returns 失败错误。)

Code: "ConcurrencyFailure"

Description: "Optimistic concurrency failure, object has been modified."

方法:

    public async Task<bool> AssignClaimsToUser(string id, List<string> newClaims)
    {
        bool success = false;

        ApplicationUser user = await _userManager.FindByIdAsync(id);
        List<Claim> userClaims = new List<Claim>();

        // Remove existing claims
        IList<Claim> existingClaims = await _userManager.GetClaimsAsync(user);
        var removal = await _userManager.RemoveClaimsAsync(user, existingClaims); // This fail

        if (removal.Succeeded)
        {
            success = true;

            // Add new claims
            foreach (string policy in newClaims)
            {
                userClaims.Add(new Claim(policy, string.Empty, ClaimValueTypes.String));
            }
            await _userManager.AddClaimsAsync(user, userClaims);
        }

        return success;
    }

为什么会发生这种情况以及如何解决此问题?

我只有在删除声明时才会遇到这个问题。我在 IdentityUser.

上调用其他方法时没有它

编辑

调试输出:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://localhost:47691/Users/ManageUsers_Update application/x-www-form-urlencoded; charset=UTF-8 246 Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware:Information: HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Identity.Application. Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization was successful for user: user@mydomain.com. Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization was successful for user: user@mydomain.com. Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method MyProject.Controllers.UsersController.ManageUsers_Update (MyProject) with arguments (Kendo.Mvc.UI.DataSourceRequest, MyProject.Views.ViewModels.ManageUsersViewModel) - ModelState is Valid Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[CustRef], [u].[Email], [u].[EmailConfirmed], [u].[IsEnabled], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName] FROM [AspNetUsers] AS [u] WHERE [u].[Id] = @__id_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__custRef_0='?' (Size = 10)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [c].[CustomerDetailsId], [c].[CustRef], [c].[CustomerDBConnectionString], [c].[Enabled], [c].[Name], [c].[UserLicenses] FROM [CustomerDetails] AS [c] WHERE [c].[CustRef] = @__custRef_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__custRef_0='?' (Size = 10)], CommandType='Text', CommandTimeout='30'] SELECT [x].[ID], [x].[AspNetUserId], [x].[CustRef], [x].[CustomerId], [x].[Email], [x].[FirstName], [x].[IsEnabled], [x].[IsMaster], [x].[ShowCosts], [x].[Surname] FROM [Users] AS [x] WHERE [x].[CustRef] = @__custRef_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__users_Id_0='?'], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [a].[ID], [a].[AspNetUserId], [a].[CustRef], [a].[CustomerId], [a].[Email], [a].[FirstName], [a].[IsEnabled], [a].[IsMaster], [a].[ShowCosts], [a].[Surname] FROM [Users] AS [a] WHERE [a].[ID] = @__users_Id_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[CustRef], [u].[Email], [u].[EmailConfirmed], [u].[IsEnabled], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName] FROM [AspNetUsers] AS [u] WHERE [u].[Id] = @__id_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[CustRef], [u].[Email], [u].[EmailConfirmed], [u].[IsEnabled], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName] FROM [AspNetUsers] AS [u] WHERE [u].[NormalizedUserName] = @__normalizedUserName_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (6ms) [Parameters=[@p16='?' (Size = 450), @p0='?', @p1='?' (Size = 4000), @p17='?' (Size = 4000), @p2='?' (Size = 4000), @p3='?' (Size = 256), @p4='?', @p5='?', @p6='?', @p7='?', @p8='?' (Size = 256), @p9='?' (Size = 256), @p10='?' (Size = 4000), @p11='?' (Size = 4000), @p12='?', @p13='?' (Size = 4000), @p14='?', @p15='?' (Size = 256)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [AspNetUsers] SET [AccessFailedCount] = @p0, [ConcurrencyStamp] = @p1, [CustRef] = @p2, [Email] = @p3, [EmailConfirmed] = @p4, [IsEnabled] = @p5, [LockoutEnabled] = @p6, [LockoutEnd] = @p7, [NormalizedEmail] = @p8, [NormalizedUserName] = @p9, [PasswordHash] = @p10, [PhoneNumber] = @p11, [PhoneNumberConfirmed] = @p12, [SecurityStamp] = @p13, [TwoFactorEnabled] = @p14, [UserName] = @p15 WHERE [Id] = @p16 AND [ConcurrencyStamp] = @p17; SELECT @@ROWCOUNT; Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__menuId_0='?'], CommandType='Text', CommandTimeout='30'] SELECT [u].[CustomerMenuId], [u].[MenuItemId] FROM [CustomerMenuItem] AS [u] WHERE [u].[CustomerMenuId] = @__menuId_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT [m].[Id], [m].[AspNetPolicyId], [m].[GlyphIcon], [m].[Label], [m].[MenuGroupId] FROM [MenuItem] AS [m] Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT [a].[Id], [a].[DefaultAction], [a].[Description], [a].[Name] FROM [AspNetPolicy] AS [a] Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[CustRef], [u].[Email], [u].[EmailConfirmed], [u].[IsEnabled], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName] FROM [AspNetUsers] AS [u] WHERE [u].[Id] = @__id_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__user_Id_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30'] SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId] FROM [AspNetUserClaims] AS [uc] WHERE [uc].[UserId] = @__user_Id_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (9ms) [Parameters=[@__8__locals1_user_Id_0='?' (Size = 450), @__claim_Value_1='?' (Size = 4000), @__claim_Type_2='?' (Size = 4000)], CommandType='Text', CommandTimeout='30'] SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId] FROM [AspNetUserClaims] AS [uc] WHERE (([uc].[UserId] = @__8__locals1_user_Id_0) AND ([uc].[ClaimValue] = @__claim_Value_1)) AND ([uc].[ClaimType] = @__claim_Type_2) Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (6ms) [Parameters=[@__8__locals1_user_Id_0='?' (Size = 450), @__claim_Value_1='?' (Size = 4000), @__claim_Type_2='?' (Size = 4000)], CommandType='Text', CommandTimeout='30'] SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId] FROM [AspNetUserClaims] AS [uc] WHERE (([uc].[UserId] = @__8__locals1_user_Id_0) AND ([uc].[ClaimValue] = @__claim_Value_1)) AND ([uc].[ClaimType] = @__claim_Type_2) Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (93ms) [Parameters=[@__8__locals1_user_Id_0='?' (Size = 450), @__claim_Value_1='?' (Size = 4000), @__claim_Type_2='?' (Size = 4000)], CommandType='Text', CommandTimeout='30'] SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId] FROM [AspNetUserClaims] AS [uc] WHERE (([uc].[UserId] = @__8__locals1_user_Id_0) AND ([uc].[ClaimValue] = @__claim_Value_1)) AND ([uc].[ClaimType] = @__claim_Type_2) Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (5ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30'] SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[CustRef], [u].[Email], [u].[EmailConfirmed], [u].[IsEnabled], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName] FROM [AspNetUsers] AS [u] WHERE [u].[NormalizedUserName] = @__normalizedUserName_0 Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (6ms) [Parameters=[@p0='?', @p1='?', @p2='?', @p19='?' (Size = 450), @p3='?', @p4='?' (Size = 4000), @p20='?' (Size = 4000), @p5='?' (Size = 4000), @p6='?' (Size = 256), @p7='?', @p8='?', @p9='?', @p10='?', @p11='?' (Size = 256), @p12='?' (Size = 256), @p13='?' (Size = 4000), @p14='?' (Size = 4000), @p15='?', @p16='?' (Size = 4000), @p17='?', @p18='?' (Size = 256)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p0; SELECT @@ROWCOUNT; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p1; SELECT @@ROWCOUNT; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p2; SELECT @@ROWCOUNT; UPDATE [AspNetUsers] SET [AccessFailedCount] = @p3, [ConcurrencyStamp] = @p4, [CustRef] = @p5, [Email] = @p6, [EmailConfirmed] = @p7, [IsEnabled] = @p8, [LockoutEnabled] = @p9, [LockoutEnd] = @p10, [NormalizedEmail] = @p11, [NormalizedUserName] = @p12, [PasswordHash] = @p13, [PhoneNumber] = @p14, [PhoneNumberConfirmed] = @p15, [SecurityStamp] = @p16, [TwoFactorEnabled] = @p17, [UserName] = @p18 WHERE [Id] = @p19 AND [ConcurrencyStamp] = @p20; SELECT @@ROWCOUNT; 'dotnet.exe' (CoreCLR: clrhost): Loaded 'C:\Users\jsmith.nuget\packages\system.diagnostics.stacktrace.3.0\lib\netstandard1.3\System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. Microsoft.EntityFrameworkCore.DbContext:Error: An exception occurred in the database while saving changes. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation 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.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.d__32.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.d__1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__47.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__45.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.DbContext.d__30.MoveNext()

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation 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.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.d__32.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.d__1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__47.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.d__45.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.DbContext.d__30.MoveNext() Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommandBuilderFactory:Information: Executed DbCommand (6ms) [Parameters=[@p0='?', @p1='?', @p2='?', @p19='?' (Size = 450), @p3='?', @p4='?' (Size = 4000), @p20='?' (Size = 4000), @p5='?' (Size = 4000), @p6='?' (Size = 256), @p7='?', @p8='?', @p9='?', @p10='?', @p11='?' (Size = 256), @p12='?' (Size = 256), @p13='?' (Size = 4000), @p14='?' (Size = 4000), @p15='?', @p16='?' (Size = 4000), @p17='?', @p18='?' (Size = 256)], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p0; SELECT @@ROWCOUNT; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p1; SELECT @@ROWCOUNT; DELETE FROM [AspNetUserClaims] WHERE [Id] = @p2; SELECT @@ROWCOUNT; UPDATE [AspNetUsers] SET [AccessFailedCount] = @p3, [ConcurrencyStamp] = @p4, [CustRef] = @p5, [Email] = @p6, [EmailConfirmed] = @p7, [IsEnabled] = @p8, [LockoutEnabled] = @p9, [LockoutEnd] = @p10, [NormalizedEmail] = @p11, [NormalizedUserName] = @p12, [PasswordHash] = @p13, [PhoneNumber] = @p14, [PhoneNumberConfirmed] = @p15, [SecurityStamp] = @p16, [TwoFactorEnabled] = @p17, [UserName] = @p18 WHERE [Id] = @p19 AND [ConcurrencyStamp] = @p20; SELECT @@ROWCOUNT; Microsoft.EntityFrameworkCore.DbContext:Error: An exception occurred in the database while saving changes. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation 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.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, DbDataReader reader) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(DbDataReader reader) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation 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.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, DbDataReader reader) at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(DbDataReader reader) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable1 commandBatches, IRelationalConnection connection) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) Exception thrown: 'Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException' in Microsoft.EntityFrameworkCore.dll The thread 0x6264 has exited with code 0 (0x0). The thread 0xaa0 has exited with code 0 (0x0). The thread 0x6a84 has exited with code 0 (0x0).

ManageUsers_Update:

    public async  Task<ActionResult> ManageUsers_Update([DataSourceRequest] DataSourceRequest request, ManageUsersViewModel viewModel)
    {
        if (viewModel != null && ModelState.IsValid)
        {
            User objToUpdate = new User
            {
                Id = viewModel.ID,
                FirstName = viewModel.FirstName,
                Surname = viewModel.Surname
            };
            _userRepository.UpdateUser(objToUpdate);

            if (viewModel.CustomerMenuId != null && viewModel.CustomerMenuId > 0)
            {
                int customerMenuId = Convert.ToInt32(viewModel.CustomerMenuId);

                // Update user's claims
                List<string> newClaims = _navigationRepository.GetPolicyNamesByCustomerMenuId(customerMenuId);
                bool claimsUpdated = await _applicationUserService.AssignClaimsToUser(viewModel.AspNetUserId, newClaims);

                // Assign user to menu if updating claims succeeded
                if (claimsUpdated)
                {
                    _navigationRepository.AssignCustomerMenuToUser(new CustomerMenuUser()
                        {CustomerMenuId = customerMenuId, AspNetUserId = viewModel.AspNetUserId});
                }
            }

            ApplicationUser user = await _userManager.FindByIdAsync(viewModel.AspNetUserId);
            user.IsEnabled = viewModel.IsEnabled;
            await _userManager.UpdateAsync(user);
        }

        return Json(new[] { viewModel }.ToDataSourceResult(request, ModelState));
    }

为什么会这样?

最可能的原因是并发冲突,这意味着多个用户(线程)已尝试更改相同的数据。

在这里您可以找到有关它如何发生的更多详细信息

https://docs.microsoft.com/en-us/ef/core/saving/concurrency

如何解决这个问题?

我假设在你的情况下,多个用户不应该更新相同的声明,因此可能存在请求重复。

你可以试试:

  • 检查数据库中的数据是否更新。如果有多个请求,其中一个应该保存更改;
  • 在发布请求时检查浏览器中的“网络”选项卡,或者放置一个断点并查看在单个请求期间它被命中了多少次;
  • 从应用程序中隔离有缺陷的代码并尝试重现该问题。

另一种方法(通常在允许多路访问时)是实施重试策略以在乐观失败的情况下重新执行更新逻辑。 我很确定这不适合您的情况,但如果您需要快速解决方案,这会起作用。

在我的案例中,解决方案很简单: 当我在产品之前删除类别(这也从 ProductCategories 中删除数据)时,我有产品、类别和 ProductCategories,我得到了一个像你一样的错误。 问题是他试图删除没有机会存在的关系,因为类别已经被删除。

在我的例子中,我在使用 _roleManager.UpdateAsync 时遇到了同样的错误,我发送了一个角色模型来更新方法,然后这给了我错误,我认为我因为这个过程而收到这个错误我正在检索数据 运行 在屏幕中加载数据(FindByIdAsync 以获取声明和其他)

执行那些从角色管理器获取的操作,因为我们正在使用 Entity framework,可能会将实体保留在内存中,所以我所做的只是在更新命令之前使用 _roleManager.FindByIdAsync,这将给我我应该更新的当前实体。

我不确定这是否有帮助,因为它有点不相关,但我遇到了代码“ConcurrencyFailure”(以及描述“乐观并发失败,对象已被修改。”)的相同并发错误以下步骤可能会帮助发现此问题的人尝试解决与我相同的问题:

  • 我创建了一个 .NET Core 5 API,它有自己的单元测试项目

  • 我创建了自己的 Domain.IdentityUser(派生自 IdentityUser)和 Domain.IdentityRole(派生自 IdentityRole,必须设置 NormalizedName 才能让 UserManager 找到角色< Domain.IdentityUser>)

  • 我创建了一个单元测试,创建了一个新的 UserManager 实例(存储构造函数参数设置为:

    new UserStore<Domain.IdentityUser, Domain.IdentityRole, ApplicationDbContext, long>(applicationDbContext)
    

    并将 UserManager 构造函数的记录器构造函数参数设置为:

    new Microsoft.Extensions.Logging.Logger<UserManager<Domain.IdentityUser>>(new Microsoft.Extensions.Logging.LoggerFactory())
    

    将 UserManager 构造函数的所有其他参数设置为 null)。

  • 存储 UserStore 没有像单元测试的其余部分一样收到相同的 ApplicationDbContext 作为构造函数参数,所以每个当单元测试调用“userManager.AddToRolesAsync(loadedUser, roles);”时,我收到了与我的一些单元测试代码打印的相同的并发错误,关于 AddToRolesAsync 返回的 IdentityResult 序列化为 json:

     {
       "Succeeded": false,
       "Errors": [
         {
           "Code": "ConcurrencyFailure",
           "Description": "Optimistic concurrency failure, object has been modified."
         }
       ]
     }
    

我不得不在单元测试中到处使用相同的 ApplicationDbContext 实例,同时从 in-memory 数据库加载数据,而不是依赖新创建的对象。