基于接口的全局筛选:能否将多实体查询筛选代码简化为一行?
如何简化EF Core多实体的全局TenantId筛选器代码?
问题:能否将以下代码简化为一行?
//为TenantId属性创建全局筛选器。 modelBuilder.Entity<Item>().HasQueryFilter(b => EF.Property<int>(b, "TenantId") == this._appUserProvider.CurrentTenantId); modelBuilder.Entity<Invite>().HasQueryFilter(b => EF.Property<int>(b, "TenantId") == this._appUserProvider.CurrentTenantId);我曾尝试为Item和Invite定义接口,但EF报错提示必须为引用类型;也试过基类方案,但不想为此修改基础表,请问还有其他可行方案吗?
当然可以简化!不用改动实体的结构(不管是加接口还是基类),这里有几个实用的方案供你选择:
方案1:封装扩展方法批量处理
把重复的筛选逻辑抽成一个扩展方法,这样就能用更简洁的方式给多个实体添加筛选器:
首先定义扩展方法:
public static class ModelBuilderExtensions { public static void AddTenantQueryFilter<TEntity>(this ModelBuilder modelBuilder, Func<int> getCurrentTenantId) where TEntity : class // 加上class约束,解决你之前遇到的"必须为引用类型"报错 { modelBuilder.Entity<TEntity>().HasQueryFilter(entity => EF.Property<int>(entity, "TenantId") == getCurrentTenantId()); } }
然后在配置的时候,只需要两行(如果实体更多可以批量循环):
// 给Item和Invite添加筛选器 modelBuilder.AddTenantQueryFilter<Item>(() => _appUserProvider.CurrentTenantId); modelBuilder.AddTenantQueryFilter<Invite>(() => _appUserProvider.CurrentTenantId);
如果后续要加更多实体,还可以把实体类型放到集合里循环处理,避免重复代码:
var tenantEntities = new[] { typeof(Item), typeof(Invite), typeof(AnotherTenantEntity) }; foreach (var entityType in tenantEntities) { var method = typeof(ModelBuilderExtensions) .GetMethod(nameof(ModelBuilderExtensions.AddTenantQueryFilter)) .MakeGenericMethod(entityType); method.Invoke(null, new object[] { modelBuilder, (Func<int>)(() => _appUserProvider.CurrentTenantId) }); }
方案2:自动遍历所有带TenantId的实体
如果你的数据库里所有包含TenantId列的实体都需要这个全局筛选器,还可以自动遍历模型中的实体类型,批量添加筛选器,完全不用手动指定每个实体:
foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { // 检查实体是否有TenantId属性,并且是引用类型(EF要求) if (entityType.FindProperty("TenantId") != null && entityType.ClrType.IsClass) { // 动态构建筛选表达式 var parameter = Expression.Parameter(entityType.ClrType, "entity"); var tenantIdProperty = Expression.Property(parameter, "TenantId"); var currentTenantId = Expression.Constant(_appUserProvider.CurrentTenantId); var filterExpression = Expression.Equal(tenantIdProperty, currentTenantId); var filterLambda = Expression.Lambda(filterExpression, parameter); // 给该实体设置全局筛选器 entityType.SetQueryFilter(filterLambda); } }
这个方案一劳永逸,后续新增带TenantId的实体时,会自动被加上筛选器,不用再修改配置代码。
补充:关于你之前的接口方案报错
你提到用接口时遇到"必须为引用类型"的错误,其实只要在泛型约束里加上class就可以解决,比如:
public interface ITenantEntity { int TenantId { get; } } // 扩展方法加上约束 public static void AddTenantQueryFilter<TEntity>(this ModelBuilder modelBuilder, Func<int> getCurrentTenantId) where TEntity : class, ITenantEntity { modelBuilder.Entity<TEntity>().HasQueryFilter(entity => entity.TenantId == getCurrentTenantId()); }
不过既然你不想修改实体结构,前面的两个方案会更适合你的需求。
内容的提问来源于stack exchange,提问作者Greg Gum
相关产品推荐
相关产品推荐

