You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 02:18:19