Blazor Server中如何向单例DbContextFactory注入作用域服务?
Blazor Server中EF Core DbContext获取当前用户信息的问题解决
背景
在Blazor Server中使用EF Core时,需通过DbContextFactory而非直接AddDbContext来注册上下文服务,注册代码如下:
builder.Services.AddDbContextFactory<AppDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("Connection")); });
该默认注册的DbContextFactory为单例生命周期。
业务需求是在DbContext的SaveChangesAsync方法中自动填充实体的创建/修改日期及用户信息,用户信息由Scoped服务UserState维护,其注册代码:
builder.Services.AddScoped<UserState>();
尝试的方案及问题
最初参考资料尝试通过IServiceScopeFactory向单例的DbContext注入Scoped服务,实现代码如下:
private readonly IServiceScopeFactory _serviceScopeFactory; public AppDbContext(DbContextOptions<AppDbContext> options, IServiceScopeFactory serviceScopeFactory) : base(options) { _serviceScopeFactory = serviceScopeFactory; } public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { using IServiceScope scope = _serviceScopeFactory.CreateAsyncScope(); var _userState = scope.ServiceProvider.GetRequiredService<UserState>(); foreach (var entry in ChangeTracker.Entries<EntityBase>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedAt = DateTime.Now; if (_userState.UserName is not null) entry.Entity.CreatedBy = _userState.UserName; break; case EntityState.Modified: entry.Entity.EditedAt = DateTime.Now; if (_userState.UserName is not null) entry.Entity.EditedBy = _userState.UserName; break; } } return base.SaveChangesAsync(cancellationToken); }
但实际运行时,通过新创建的作用域获取到的UserState是一个空数据的新实例——原因是手动创建的作用域与当前用户的Scoped作用域完全独立,无法共享已填充的用户状态。
解决方法
将DbContextFactory的生命周期改为Scoped,这样它就能和UserState处于同一作用域中,直接注入即可获取正确的用户信息。
1. 修改DbContextFactory注册代码
builder.Services.AddDbContextFactory<AppDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("Connection")); }, ServiceLifetime.Scoped);
2. 调整DbContext构造函数与SaveChangesAsync实现
直接注入UserState,无需再手动创建作用域:
private readonly UserState _userState; public AppDbContext(DbContextOptions<AppDbContext> options, UserState userState) : base(options) { _userState = userState; } public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { foreach (var entry in ChangeTracker.Entries<EntityBase>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedAt = DateTime.Now; if (_userState.UserName is not null) entry.Entity.CreatedBy = _userState.UserName; break; case EntityState.Modified: entry.Entity.EditedAt = DateTime.Now; if (_userState.UserName is not null) entry.Entity.EditedBy = _userState.UserName; break; } } return base.SaveChangesAsync(cancellationToken); }
此方案下,每次通过Scoped的DbContextFactory创建的DbContext,都能直接获取到当前用户的UserState实例,自动填充实体的用户信息。
内容的提问来源于stack exchange,提问作者Luis Zenteno
相关产品推荐
相关产品推荐

