依赖注入UserManager引发并发问题:如何使其使用不同DbContext?
解决Blazor Server中UserManager共享DbContext导致的并发操作错误
核心问题原因
Blazor Server的Scoped服务与用户电路(Circuit)绑定,默认UserManager<TUser>以Scoped方式注册,同一用户的所有组件会共享同一个UserManager实例,进而复用其依赖的DbContext。当多个组件同时通过UserManager操作数据库时,就会触发"A second operation was started on this context instance before a previous operation completed"错误。
解决方案:让UserManager每次注入使用全新DbContext
步骤1:自定义UserStore,依赖DbContextFactory
创建自定义UserStore,通过IDbContextFactory<myContext>创建全新的DbContext实例,替代直接依赖已有DbContext的逻辑:
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Linq; public class CustomUserStore : UserStore<IdentityUser> { public CustomUserStore(IDbContextFactory<myContext> contextFactory, IOptions<IdentityOptions> optionsAccessor = null, IPasswordHasher<IdentityUser> passwordHasher = null, IEnumerable<IUserValidator<IdentityUser>> userValidators = null, IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators = null, ILookupNormalizer keyNormalizer = null, IdentityErrorDescriber errors = null, IServiceProvider services = null, ILogger<UserStore<IdentityUser>> logger = null) : base(contextFactory.CreateDbContext(), optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { } }
步骤2:调整服务注册逻辑
在Program.cs中,先注册DbContextFactory,再替换默认UserStore为自定义实现,并将UserManager<TUser>注册为Transient,确保每次注入都获取全新实例:
// 注册DbContext工厂 builder.Services.AddDbContextFactory<myContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // 注册自定义UserStore为Transient builder.Services.AddTransient<IUserStore<IdentityUser>, CustomUserStore>(); // 添加Identity服务,并将UserManager注册为Transient builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddUserManager<UserManager<IdentityUser>>() .AddEntityFrameworkStores<myContext>();
步骤3:验证效果
修改后,每个组件注入的UserManager都是全新实例,且每个实例使用的DbContext都是工厂创建的新实例,不会再出现并发操作同一DbContext的问题,无需再用SemaphoreSlim做同步。
补充说明
- 直接将
DbContext注册为Transient无法解决问题:因为默认UserManager是Scoped,会在其生命周期内复用同一个Transient的DbContext实例。 - 将
UserManager注册为Transient是关键:确保每次组件注入时都能拿到新的UserManager,自定义UserStore则保证每个UserManager使用的DbContext是全新的。
内容的提问来源于stack exchange,提问作者Qrt
相关产品推荐
相关产品推荐

