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

EF Core:如何在不生成基类数据表的情况下应用全局查询过滤器

问题:如何在不生成基类数据表的情况下为继承结构应用全局查询过滤器?

实体结构

我们有如下抽象基类及子类:

public abstract class BaseClass
{
    public int Id { get; set; }
    public string BaseProperty { get; set; }
}

public class ChildClassOne : BaseClass
{
    public string PropertyOne { get; set; }
}

public class ChildClassTwo : BaseClass
{
    public string PropertyTwo { get; set; }
}

初始配置与迁移结果

初始模型配置中,我们为每个子类指定独立数据表:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<ChildClassOne>().ToTable("ChildsOne");
    modelBuilder.Entity<ChildClassTwo>().ToTable("ChildsTwo");
}

此时生成的迁移只会创建两个子表,基类的属性会直接嵌入子表中,不会生成基类对应的数据表:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "ChildsOne",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            PropertyOne = table.Column<string>(type: "TEXT", nullable: false),
            BaseProperty = table.Column<string>(type: "TEXT", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_ChildsOne", x => x.Id);
        });

    migrationBuilder.CreateTable(
        name: "ChildsTwo",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            PropertyTwo = table.Column<string>(type: "TEXT", nullable: false),
            BaseProperty = table.Column<string>(type: "TEXT", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_ChildsTwo", x => x.Id);
        });
}

添加全局查询过滤器后的异常情况

当我们尝试直接为基类添加全局查询过滤器时:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // 全局查询过滤器
    modelBuilder.Entity<BaseClass>().HasQueryFilter(x => x.Id == x.Id);

    modelBuilder.Entity<ChildClassOne>().ToTable("ChildsOne");
    modelBuilder.Entity<ChildClassTwo>().ToTable("ChildsTwo");
}

EF会自动将基类识别为需要映射的实体,生成的迁移会创建基类数据表,同时子表仅保留自身属性并通过外键关联基类表:

protected override void Up(MigrationBuilder migrationBuilder)
{
    // 基类对应的数据表被创建
    migrationBuilder.CreateTable(
        name: "BaseClass",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            BaseProperty = table.Column<string>(type: "TEXT", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_BaseClass", x => x.Id);
        });

    migrationBuilder.CreateTable(
        name: "ChildsOne",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            PropertyOne = table.Column<string>(type: "TEXT", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_ChildsOne", x => x.Id);
            table.ForeignKey(
                name: "FK_ChildsOne_BaseClass_Id",
                column: x => x.Id,
                principalTable: "BaseClass",
                principalColumn: "Id",
                onDelete: ReferentialAction.Cascade);
        });

    migrationBuilder.CreateTable(
        name: "ChildsTwo",
        columns: table => new
        {
            Id = table.Column<int>(type: "INTEGER", nullable: false)
                .Annotation("Sqlite:Autoincrement", true),
            PropertyTwo = table.Column<string>(type: "TEXT", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_ChildsTwo", x => x.Id);
            table.ForeignKey(
                name: "FK_ChildsTwo_BaseClass_Id",
                column: x => x.Id,
                principalTable: "BaseClass",
                principalColumn: "Id",
                onDelete: ReferentialAction.Cascade);
        });
}

解决方案:为子类批量配置查询过滤器

要避免生成基类数据表,我们不能直接对BaseClass的Entity配置过滤器,而是需要遍历所有继承自BaseClass的子类,逐个为它们的Entity配置过滤器。

方法1:直接遍历子类配置

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // 遍历所有BaseClass的非抽象子类
    var baseType = typeof(BaseClass);
    var childTypes = Assembly.GetExecutingAssembly().GetTypes()
        .Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t));

    foreach (var type in childTypes)
    {
        // 通过反射获取EntityTypeBuilder并配置过滤器
        var entityMethod = typeof(ModelBuilder).GetMethod("Entity", Type.EmptyTypes)
            .MakeGenericMethod(type);
        var entityBuilder = entityMethod.Invoke(modelBuilder, null);

        // 构造过滤器表达式(示例为x => x.Id == x.Id,可替换为实际逻辑)
        var parameter = Expression.Parameter(type, "x");
        var left = Expression.Property(parameter, nameof(BaseClass.Id));
        var right = Expression.Property(parameter, nameof(BaseClass.Id));
        var filterExpression = Expression.Lambda(Expression.Equal(left, right), parameter);

        var filterMethod = typeof(EntityTypeBuilder<>).MakeGenericType(type)
            .GetMethod("HasQueryFilter", new[] { filterExpression.GetType() });
        filterMethod.Invoke(entityBuilder, new[] { filterExpression });
    }

    modelBuilder.Entity<ChildClassOne>().ToTable("ChildsOne");
    modelBuilder.Entity<ChildClassTwo>().ToTable("ChildsTwo");
}

方法2:封装扩展方法简化操作

可以封装一个通用扩展方法,批量为子类配置过滤器:

public static class ModelBuilderExtensions
{
    public static void HasQueryFilterForDerivedTypes<TBase>(this ModelBuilder modelBuilder, Expression<Func<TBase, bool>> filter)
        where TBase : class
    {
        var baseType = typeof(TBase);
        var childTypes = Assembly.GetExecutingAssembly().GetTypes()
            .Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t));

        foreach (var type in childTypes)
        {
            // 将基类过滤器表达式转换为子类适用的表达式
            var parameter = Expression.Parameter(type, "x");
            var convertedExpression = Expression.Invoke(filter, parameter);
            var lambda = Expression.Lambda(convertedExpression, parameter);

            var entityBuilder = modelBuilder.Entity(type);
            var method = typeof(EntityTypeBuilder<>).MakeGenericType(type)
                .GetMethod("HasQueryFilter", new[] { lambda.GetType() });
            method.Invoke(entityBuilder, new[] { lambda });
        }
    }
}

在OnModelCreating中直接调用:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    // 为所有BaseClass的子类应用过滤器
    modelBuilder.HasQueryFilterForDerivedTypes<BaseClass>(x => x.Id == x.Id);

    modelBuilder.Entity<ChildClassOne>().ToTable("ChildsOne");
    modelBuilder.Entity<ChildClassTwo>().ToTable("ChildsTwo");
}

这样配置后,生成的迁移会保持初始的TPT结构(仅创建两个子表,无基类表),同时所有子类的查询都会自动应用指定的全局过滤器。


内容的提问来源于stack exchange,提问作者Qwertyluk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:05:25