UserManager 错误“在前一个操作完成之前在此上下文上启动了第二个操作”

Posted

技术标签:

【中文标题】UserManager 错误“在前一个操作完成之前在此上下文上启动了第二个操作”【英文标题】:UserManager error "A second operation started on this context before a previous operation completed" 【发布时间】:2020-06-05 20:57:26 【问题描述】:

在我的 Blazor 服务器端应用程序中,我有多个组件,每个组件都通过依赖注入使用 UserManager,这些组件通常呈现在同一页面上。例如,我使用 NavMenu 中的 UserManager 向用户显示/隐藏某些导航项,然后在页面本身内设置逻辑以防止导航到页面本身中的相同页面。通常在导航到具有此逻辑的页面时,NavMenu 和 Page UserManager 似乎会发生冲突,从而导致错误:

InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

我确信这是其他人遇到的问题,但无法找到解决方案。如果我在包含多个带有注入 UserManager 的组件的页面上点击刷新,则最常发生这种情况。我很感激可以提供的任何帮助,如果需要,可以提供更多信息!

根据要求,这是我对 UserManager 的注册。 ApplicationUserManager 目前实际上并没有覆盖 UserManager 中的任何功能,只是为未来的定制/增强而实现:

            services.AddIdentity<WS7.Engine.Models.Identity.ApplicationUser, WS7.Engine.Models.Identity.ApplicationRole>(options =>
            
                options.SignIn.RequireConfirmedAccount = true;
                options.User.RequireUniqueEmail = true;

                options.Password.RequireDigit = true;
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.RequiredLength = 6;
                options.Password.RequiredUniqueChars = 1;

            )
            .AddEntityFrameworkStores<WS7.Areas.Identity.Data.ApplicationIdentityContext>()
            .AddUserManager<ApplicationUserManager>()
            .AddSignInManager<ApplicationSignInManager>()
            .AddRoles<ApplicationRole>()
            .AddDefaultTokenProviders();

可能值得注意的是,似乎出现此错误的调​​用(基于堆栈跟踪)都在所涉及的各个组件的 OnInitializedAsync() 方法中。

两个组件的示例: 组件 1:

 protected override async Task OnInitializedAsync()
    
        await base.OnInitializedAsync();
        await Authorize();
    

    private async Task Authorize()
    
        bool allowNavigate = (AllowedRoleIds.Count() == 0);
        var contextUser = _AuthorizeHttpContextAccessor.HttpContext.User;
        if (contextUser != null)
        
            var user = await _AuthorizeUserManager.GetUserAsync(contextUser);
            if (user != null)
            
                var result = await _AuthorizeIdentityService.GetUserRightsAsync(new Engine.Models.GetUserRightsParams()
                
                    UserID = user.Id
                );
                if (result.Succeeded == Engine.Models.Base.SuccessState.Succeeded)
                
                    if (result.UserRightIDs.Any(uri => AllowedRoleIds.Split(",").Any(ari => ari.Equals(uri, StringComparison.CurrentCultureIgnoreCase))))
                    
                        allowNavigate = true;
                    
                
            
        
        if (allowNavigate == false)
        
            _AuthorizeNavigationManager.NavigateTo("/Identity/Account/Login");
        
    

组件 2:

 protected override async Task OnInitializedAsync()
    
        await RefreshData();
        await base.OnInitializedAsync();
    

    private async Task RefreshData()
    
        var userAccountsResult = await _IdentityService.GetAspNetUserAccountsAsync(new Engine.Models.GetAspNetUserAccountsParams()
        
            //Return all. Don't set any Params
        );
        if (userAccountsResult.Succeeded == SuccessState.Succeeded)
        
            var users = await _userManager.Users.ToListAsync();
            var usersView = users.Select(u => new UserViewModel()
            
                Id = u.Id,
                UserName = u.UserName,
                FirstName = u.FirstName,
                LastName = u.LastName,
                Email = u.Email,
                AccountStatus = u.ApprovedStatus,
                EmailConfirmed = u.EmailConfirmed,
                Active = !u.InActive,
                UserAccounts = userAccountsResult.UserAccounts.Where(ua => ua.UserID == u.Id).Select(ua => new UserAccountModel()
                
                    Account = ua.Account
                ).ToList()
            ).ToList();

            Users = usersView;
            FilteredUsers = usersView;
        
        else
        
            _StatusService.SetPageStatusMessage(new PageStatusMessageEventArgs()
            
                AlertType = AlertType.Danger,
                Message = "There was an issue initializing the page."
            );

        

    

一个示例异常的堆栈跟踪:

System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913. at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at WS7.Areas.Identity.Data.ApplicationUserManager.FindByIdAsync(String userId) in C:\VB6\Web\WS7\WS7\Areas\Identity\Data\ApplicationUserManager.cs:line 31 at WS7.Areas.Identity.Data.ApplicationUserManager.GetUserAsync(ClaimsPrincipal principal) in C:\VB6\Web\WS7\WS7\Areas\Identity\Data\ApplicationUserManager.cs:line 35 at WS7.Components.PDAuthorizeBase.Authorize() in C:\VB6\Web\WS7\WS7\Components\PDAuthorizeBase.cs:line 51 at WS7.Components.PDAuthorizeBase.OnInitializedAsync() in C:\VB6\Web\WS7\WS7\Components\PDAuthorizeBase.cs:line 35

【问题讨论】:

@HenkHolterman 我已将我的 UserManager 注册码添加到主帖中。此外,我不相信我有任何遗漏的等待或任何异步无效,但我会重新审查我的项目。我在 Visual Studio 的“错误列表”窗口中没有显示任何警告。 @HenkHolterman 我有两个碰撞组件的示例。谢谢! @HenkHolterman 另外,澄清一下,_IdentityService 对象是一项服务,它通过 WebApi 执行某些身份相邻操作 - 用户管理器无法完成的数据检索/操作(例如自定义表管理) @HenkHolterman 我在帖子末尾添加了一个堆栈跟踪。谢谢! UserManager 是 WS7 项目中唯一可访问的 DBContext;其他一切目前都通过 WebAPI 在一个单独的项目中处理。 UserManager 不总是单例吗?有没有办法以其他方式声明它?由于异步版本不接受 ClaimsPrincipal 对象,因此我对 GetUserID 或 GetUserName 非异步进行了几次调用-这些可能相关吗?我在帖子中添加了它,但这主要发生在我刷新页面并创建与服务器的新连接时。 【参考方案1】:

我认为您可能会在这里找到解决问题的方法:

在这些情况下,您应该使用OwningComponentBase&lt;T&gt;。下面是一个 显示正确模式的更新示例。

Read this。 See also this...

您是否在 Server Blazor 应用程序中使用 HttpContext ?如果你这样做,你不应该, 因为 Server Blazor App 不是基于 HTTP 的应用程序,而是基于 WebSocket 的应用程序。

希望这会有所帮助...

【讨论】:

如果我理解正确,根据链接中提供的示例项目,将我的组件切换到 OwningComponentBase 并注入 UserManager 应该可以解决此问题?我尝试了这个但没有成功。另外,我目前正在通过 IHttpContextAccessor 获取 HTTPContext。使用 AuthenticationStateProvider 的方法是否正确? 抱歉,由于我现在比较忙,所以现在我无法帮助您解决主要问题。但是,我建议您更改 DBContext 的范围。以下链接是我的帖子如何在最初访问您的应用程序时访问 HttpContext,在建立 WebSocket 连接之前,这是 HttpContext 唯一可用的时间。关于您关于 AuthenticationStateProvider 的问题,答案是肯定的。您必须非常认真地学习如何使用此服务和其他相关组件。这就是 Blazor 的发展方向。 感谢 AuthenticationStateProvider 的提示!有没有办法改变 UserManager 中 DBContext 的范围?到目前为止,我试图这样做但没有成功。

以上是关于UserManager 错误“在前一个操作完成之前在此上下文上启动了第二个操作”的主要内容,如果未能解决你的问题,请参考以下文章

Blazor:在前一个操作完成之前在此上下文上启动了第二个操作

Entity Framework Core:在前一个操作完成之前在此上下文上启动了第二个操作

asp.net core 在前一个操作完成之前在此上下文上启动了第二个操作

对 ITVF 的引用会引发“在前一个操作完成之前在此上下文上启动的第二个操作”异常

UserManager.AddToRole 不工作 - 外键错误

错误“未为 UserManager 类型定义方法 createUser(String, int)”