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

在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实例,再在查询过滤器中引用该实例级参数。

具体实现步骤

  1. 在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)
        {
        }
    
        // 原有属性和方法...
    }
    
  2. 修改查询过滤器表达式,引用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
    }
    
  3. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:35:37