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
相关产品推荐
相关产品推荐

