.NET Identity多租户应用:DbContext用户ID注入与查询过滤器问题
解决.NET Identity多租户应用中用户数据隔离的查询过滤器问题
你的问题核心在于EF Core的模型构建(OnModelCreating)只在应用启动时执行一次并缓存,此时传入配置类的用户ID是空值,导致后续查询过滤器失效。不需要手动在每个查询加过滤,通过动态查询过滤器就能解决,具体方案如下:
1. 重构配置类,移除静态用户ID依赖
先把实体配置类里的硬编码用户ID参数去掉,只保留表结构、关系等静态配置:
internal sealed class UserInfoConfiguration : IEntityTypeConfiguration<UserInfo> { public void Configure(EntityTypeBuilder<UserInfo> builder) { builder.ToTable("UserInfo"); builder.HasKey(x => x.Id); builder.HasOne<AppUser>(x => x.AppUser) .WithOne(x => x.UserInfo) .HasForeignKey<UserInfo>(x => x.AppUserId) .IsRequired(); // 移除这里的HasQueryFilter,后续在DbContext中动态添加 } }
EmailInfoConfiguration、SocialMediaInfoConfiguration做同样修改。
2. 在DbContext中动态添加查询过滤器
利用DbContext的Scoped生命周期(每个请求一个实例),直接在OnModelCreating中引用IAppUserResolver,让过滤器在每次查询时动态获取当前用户ID:
public class AppDbContext : IdentityDbContext<AppUser> { private readonly IAppUserResolver _appUserResolver; public AppDbContext(DbContextOptions<AppDbContext> options, IAppUserResolver appUserResolver) : base(options) { _appUserResolver = appUserResolver; } // 封装获取当前用户ID的方法,方便过滤器引用 private string GetCurrentUserId() => _appUserResolver.GetCurrentAppUserId(); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 先应用实体的静态配置 modelBuilder.ApplyConfiguration(new UserInfoConfiguration()); modelBuilder.ApplyConfiguration(new EmailInfoConfiguration()); modelBuilder.ApplyConfiguration(new SocialMediaInfoConfiguration()); // 动态添加查询过滤器,每次查询时都会求值GetCurrentUserId() modelBuilder.Entity<UserInfo>() .HasQueryFilter(x => x.AppUserId == GetCurrentUserId()); modelBuilder.Entity<EmailInfo>() .HasQueryFilter(x => x.AppUserId == GetCurrentUserId()); modelBuilder.Entity<SocialMediaInfo>() .HasQueryFilter(x => x.AppUserId == GetCurrentUserId()); } }
3. 原理说明
- EF Core的查询过滤器允许引用DbContext实例的成员,而DbContext是Scoped生命周期,每个请求都会创建新实例,注入的IAppUserResolver也是Scoped的,所以每次查询时都会从当前请求的HttpContext中获取正确的用户ID。
- 模型缓存的是过滤器的lambda表达式结构,而不是表达式的求值结果,所以每次执行查询时都会重新计算GetCurrentUserId()的值。
额外注意事项
- 处理匿名用户:如果GetCurrentAppUserId()返回null,过滤器会自动过滤掉所有数据,符合匿名用户无法查看任何用户数据的预期。如果需要特殊处理(比如允许匿名访问公共数据),可以修改过滤器逻辑,例如:
x => x.IsPublic || x.AppUserId == GetCurrentUserId()。 - 确保DbContext生命周期:默认
AddDbContext注册的是Scoped生命周期,不要改为Singleton,否则会导致用户ID串用。
内容的提问来源于stack exchange,提问作者athagiorgos
相关产品推荐
相关产品推荐

