Blazor中使用IDbContextFactory获取登录用户的ApplicationDbContext问题
解决Blazor中IDbContextFactory结合用户身份的DbContext使用问题
核心问题分析
你遇到的报错根源是服务生命周期不匹配:IDbContextFactory是单例注册的,而AuthenticationStateProvider是作用域服务(与Blazor的SignalR连接绑定)。单例服务无法依赖作用域服务,否则会导致服务无法正确释放或获取到过期实例。
推荐解决方案:重构DbContext,在组件层传递用户信息
官方推荐使用IDbContextFactory创建独立的DbContext实例,避免组件间共享上下文引发的并发问题。我们可以调整DbContext的设计,不在构造函数中依赖AuthenticationStateProvider,而是在组件中先获取用户身份,再传递给DbContext。
1. 重构ApplicationDbContext
移除对AuthenticationStateProvider的构造依赖,添加属性存储当前用户ID,在SaveChanges中使用该属性处理审计逻辑:
public class ApplicationDbContext : DbContext { // 存储当前操作的用户ID public string CurrentUserId { get; set; } public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { ApplyAuditLogic(); return await base.SaveChangesAsync(cancellationToken); } public override int SaveChanges() { ApplyAuditLogic(); return base.SaveChanges(); } private void ApplyAuditLogic() { if (string.IsNullOrEmpty(CurrentUserId)) return; var entries = ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified); foreach (var entry in entries) { if (entry.State == EntityState.Added) { entry.Property("CreatedBy").CurrentValue = CurrentUserId; entry.Property("CreatedAt").CurrentValue = DateTime.UtcNow; } entry.Property("UpdatedBy").CurrentValue = CurrentUserId; entry.Property("UpdatedAt").CurrentValue = DateTime.UtcNow; } } }
2. 注册DbContextFactory
在Program.cs中注册工厂并传入连接字符串:
builder.Services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
3. 在Blazor组件中使用
注入IDbContextFactory和AuthenticationStateProvider,先获取用户身份,再创建DbContext并设置用户ID:
@inject IDbContextFactory<ApplicationDbContext> DbContextFactory @inject AuthenticationStateProvider AuthStateProvider <button @onclick="SaveData">保存数据</button> @code { private async Task SaveData() { // 获取当前用户身份 var authState = await AuthStateProvider.GetAuthenticationStateAsync(); var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; // 创建独立的DbContext实例 using var dbContext = DbContextFactory.CreateDbContext(); dbContext.CurrentUserId = userId; // 执行数据操作 dbContext.YourEntities.Add(new YourEntity { Name = "示例数据" }); await dbContext.SaveChangesAsync(); } }
备选方案:通过作用域解析DbContext
如果必须在DbContext内部依赖AuthenticationStateProvider,可以在组件中手动创建作用域,在作用域内解析DbContext(此时DbContext会继承作用域生命周期,能正确获取作用域的AuthenticationStateProvider):
1. 注册Scoped的DbContext
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
2. 在组件中创建作用域并使用
@inject IServiceProvider ServiceProvider <button @onclick="SaveData">保存数据</button> @code { private async Task SaveData() { // 创建独立作用域 using var scope = ServiceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); // 此时DbContext能正确依赖注入AuthenticationStateProvider await dbContext.SaveChangesAsync(); } }
注意:这种方式需要确保每个数据操作都创建新的作用域,避免组件间共享DbContext实例。
内容的提问来源于stack exchange,提问作者ekenman
相关产品推荐
相关产品推荐

