Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UserManager error "A second operation started on this context before a previous operation completed"

In my Blazor Server-side app, I have multiple components which each use the UserManager via dependency injection that are often rendered on the same page. For example, I use the UserManager in the NavMenu to show/hide certain navigation items from the user, then have logic within the pages themselves to prevent navigation to those same pages in the pages themselves. Often when navigating to a page that has this logic, the NavMenu and Page UserManager are appearing to collide, resulting in the error:

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.

I am sure that this is an issue others have run into, but haven't been able to find a solution. This occurs most often if I hit Refresh on a page containing multiple components with an injected UserManager. I appreciate any help that can be provided, and can provide more information if needed!

Per request, here is my registration of the UserManager. ApplicationUserManager is currently not actually overriding any functionality in the UserManager, just implemented for future customization/enhancements:

            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();

Possibly worth noting, the calls that seem to be blowing out with this error (based on the stack trace) are both in the OnInitializedAsync() methods of the various components involved.

Example of two components: Component 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");
        }
    }

Component 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."
            });

        }

    }

Stack Trace of an example exception:

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
like image 762
dingdangdowney Avatar asked Sep 12 '25 01:09

dingdangdowney


1 Answers

I think you might find a solution to your issue here:

You should use OwningComponentBase<T> in these situations. Below is an updated sample that shows the right pattern.

Read this. See also this...

Are you using HttpContext in your Server Blazor App ? If you does, you shouldn't, as Server Blazor App are not HTTP-based apps, but WebSocket-based ones.

Hope this helps...

like image 191
enet Avatar answered Sep 14 '25 17:09

enet