在OnModelCreating中查询DbContext实现TenantId过滤的问题
我正在使用.NET Core 5和Entity Framework Core,已通过SetQueryFilters结合ActiveId实现软删除功能。我还希望为带有TenantId列的特定表添加过滤规则,仅显示与当前用户所属组织匹配的记录(即TenantId属于OrganizationId列表)。现有代码如下:
public partial class AppContext : DbContext { public AppContext (DbContextOptions<AppContext> options) : base(options) { } #region Generated Properties ... #endregion protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var isActiveProperty = entityType.FindProperty("ActiveId"); var isTenantProperty = entityType.FindProperty("TenantId"); if (isTenantProperty != null && isTenantProperty.ClrType == typeof(Int32) && isActiveProperty != null && isActiveProperty.ClrType == typeof(Int32)) { List<int> Ids = ///--Ids.NEEDTOGRABTHESE var parameter = Expression.Parameter(entityType.ClrType, "p"); var filter = Expression.Lambda( Expression.And( Expression.OrElse( Expression.Equal(Expression.Property(parameter, "ActiveId"), Expression.Constant((int)ActiveCodes.ACTIVE)), Expression.Equal(Expression.Property(parameter, "ActiveId"), Expression.Constant((int)ActiveCodes.BLOCKED)) ), Expression.Equal(Expression.Property(parameter, "TenantId"), ///--Ids.NEEDTOGRABTHESE) ), parameter); entityType.SetQueryFilter(filter);//Update for ef 3.0 } } #region Generated Configuration ... #endregion } }
尝试在OnModelCreating中查询数据库获取当前用户的组织ID时,引用上下文会报错:
An attempt was made to use the model while it was being created. A DbContext instance cannot be used inside 'OnModelCreating' in any way that makes use of the model that is being created.
由于获取当前用户组织只能通过数据库查询,但此时DbContext无法访问,请问如何在OnModelCreating中访问DbContext?另外,若提供解决方案,该方案每次在OnModelCreating执行时是否高效、是否会缓存结果?
核心思路:避免在OnModelCreating中处理动态数据
OnModelCreating是EF Core构建模型的初始化阶段,模型构建完成后会被全局缓存复用,因此这里不能依赖动态变化的用户组织ID,也无法直接调用DbContext执行查询。正确的做法是将动态参数注入DbContext实例,再在查询过滤器中引用该实例级参数。
具体实现步骤
在DbContext中添加存储用户组织ID的属性
这个属性会在DbContext实例创建时被赋值,用于存储当前用户的组织ID集合:public partial class AppContext : DbContext { // 存储当前用户的组织ID列表,实例化时注入 public IReadOnlyList<int> CurrentUserOrganizationIds { get; set; } = new List<int>(); public AppContext(DbContextOptions<AppContext> options) : base(options) { } // 原有属性和方法... }修改查询过滤器表达式,引用DbContext实例属性
通过表达式树构建对CurrentUserOrganizationIds的引用,用Contains方法实现列表匹配(替代原代码中的Equal,更符合业务需求):protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var isActiveProperty = entityType.FindProperty("ActiveId"); var isTenantProperty = entityType.FindProperty("TenantId"); if (isTenantProperty != null && isTenantProperty.ClrType == typeof(int) && isActiveProperty != null && isActiveProperty.ClrType == typeof(int)) { var parameter = Expression.Parameter(entityType.ClrType, "p"); // 构建ActiveId过滤逻辑 var activeFilter = Expression.OrElse( Expression.Equal(Expression.Property(parameter, "ActiveId"), Expression.Constant((int)ActiveCodes.ACTIVE)), Expression.Equal(Expression.Property(parameter, "ActiveId"), Expression.Constant((int)ActiveCodes.BLOCKED)) ); // 构建TenantId过滤逻辑:引用DbContext的CurrentUserOrganizationIds,调用Contains方法 var dbContextInstance = Expression.Constant(this); var organizationIdsProp = Expression.Property(dbContextInstance, nameof(CurrentUserOrganizationIds)); var tenantIdProp = Expression.Property(parameter, "TenantId"); var containsMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Contains), new[] { typeof(IEnumerable<int>), typeof(int) }); var tenantFilter = Expression.Call(containsMethod, organizationIdsProp, tenantIdProp); // 组合两个过滤条件 var combinedFilter = Expression.And(activeFilter, tenantFilter); var filterLambda = Expression.Lambda(combinedFilter, parameter); entityType.SetQueryFilter(filterLambda); } } // 原有配置代码... #region Generated Configuration ... #endregion }在DbContext实例化时注入当前用户组织ID
以ASP.NET Core依赖注入为例,通过自定义服务获取当前用户信息,再注入到DbContext实例:// Program.cs builder.Services.AddScoped(provider => { var dbOptions = provider.GetRequiredService<DbContextOptions<AppContext>>(); var userService = provider.GetRequiredService<ICurrentUserService>(); // 自定义服务,负责获取当前用户信息 var organizationIds = userService.GetUserOrganizationIds(); // 从数据库查询用户所属组织ID return new AppContext(dbOptions) { CurrentUserOrganizationIds = organizationIds }; });
效率与缓存说明
- 模型缓存:
OnModelCreating仅执行一次,模型构建完成后会被EF Core全局缓存,不会随DbContext实例创建重复执行,过滤器表达式逻辑初始化开销极低。 - 动态参数复用:
CurrentUserOrganizationIds是DbContext实例级属性,每个请求(或每个DbContext实例)都会注入对应用户的组织ID,过滤器会自动使用该实例的参数,无需重新构建表达式。 - 查询性能:EF Core会将
Contains表达式转换为SQL的IN语句,若TenantId字段有索引,数据库可直接优化查询,性能与手动编写WHERE TenantId IN (...)一致。
内容的提问来源于stack exchange,提问作者ModestMonk

