Entity Framework Core 实现全局查询过滤


微软在 Entity Framework Core 2+ 中引入了全局查询过滤器,简化了构建多租户应用程序和实体软删除的复杂度。这篇文章我将通过代码的形式对全局过滤查询进行详细的讲解。在讲解前我们先来简单说一下什么是多租户,所谓多租户简单来说是指一个单独的实例可以为多个组织服务。多租户技术为共用的数据中心内如何以单一系统架构与服务提供多数客户端相同甚至可定制化的服务,并且仍然可以保障客户的数据隔离。
接下来我们先来看一个例子,我们假定多个租户使用同一个数据库,同一个Schema,区分租户是根据表中的 tId 区分。我们新建一个项目,在项目中重写 DbContext 上下文里的 OnModelCreating 方法,在这个方法中我们使用 HasQueryFilter 方法进行软删除。
public?class?EFContext?:?DbContext
{
????protected?override?void?OnModelCreating(ModelBuilder?modelBuilder)
????{
????????modelBuilder.Entity().HasQueryFilter(p?=>?!p.IsDelete);
????????modelBuilder.Entity().HasQueryFilter(p?=>?!p.IsDelete);
????????base.OnModelCreating(modelBuilder);
????}
}

我们首先准备 Model 代码,代码很简单:
///?
///?实体基类
///?
public?class?BaseModel
{
????public?int?Id?{?get;?set;?}
????//租户Id
????public?int?TId?{?get;?set;?}
????//是否删除(软删除)
????public?bool?IsDelete?{?get;?set;?}
}
///?
///?员工类
///?
public?class?Employee:BaseModel
{
????public?string?Name?{?get;?set;?}
????public?int?Age?{?get;?set;?}
????public?Department?Department?{?get;?set;?}
}
///?
///?部门类
///?
public?class?Department:BaseModel
{
????public?string?DepName?{?get;?set;?}
????public?int?EmployeeId?{?get;?set;?}
}
public?class?EmployeeConfiguration?:?IEntityTypeConfiguration< Employee>
{
????public?void?Configure(EntityTypeBuilder?builder)
????{
????????builder.ToTable("Employee");
????????builder.HasKey(p?=>?p.Id);
????????builder.Property(p?=>?p.Name);
????????builder.Property(p?=>?p.IsDelete);
????????builder.Property(p?=>?p.Age);
????????builder.HasQueryFilter(p?=>?!p.IsDelete);
????}
}
public?class?DepartmentConfiguration?:?IEntityTypeConfiguration< Department>
{
????public?void?Configure(EntityTypeBuilder?builder)
????{
????????builder.ToTable("Department");
????????builder.HasKey(p?=>?p.Id);
????????builder.Property(p?=>?p.DepName);
????????builder.Property(p?=>?p.IsDelete);
????????builder.HasQueryFilter(p?=>?!p.IsDelete);
????????builder.HasMany(p?=>?p.Employees).WithOne(e?=>?e.Department).HasForeignKey(k?=>?k.DepartmentId);
????}
}
public?interface?ITenantProvider
{
????int?GetTId();
}
public?class?TenantProvider?:?ITenantProvider
{
????public?int?GetTId()
????{
????????return?1;
????}
}
public?interface?IDepartmentDb
{
????IQueryable?GetDepartments() ;
}
public?class?DepartmentDb?:?IDepartmentDb
{
????private?EFContext?ef;
????public?DepartmentDb(EFContext?_ef)
????{
????????ef?=?_ef;
????}
????public?IQueryable?GetDepartments()
???? {
????????var?departments?=?ef.Departments;
????????return?departments;
????}
}

如果要为所有实体配置全局查询过滤器,就必须能够自动检测出实体类型,同时类型检测时必须具有缓存支持。基于这两条我们动手创建获取实体类型的接口和实现。首先利用 DependencyContext 获取运行时程序集,将获得的程序集添加到集合中,然后查找出继承自基类 BaseModel 的程序集,如果查找到了就返回,如果没有查找到就实现全局过滤缓存,代码如下:
public?interface?IEntityTypeProvider
{
?????IEnumerable?GetTypes() ;
}
public?class?EntityTypeProvider?:?IEntityTypeProvider
{
????IList?entityTypeCache;
????public?IEnumerable?GetTypes()
???? {
????????if(entityTypeCache!=null)
????????{
????????????return?entityTypeCache.ToList();
????????}
????????entityTypeCache?=?(from?a?in?GetReferencingAssemblies()
???????????????????????????from?t?in?a.DefinedTypes
???????????????????????????where?t.BaseType?==?typeof(BaseModel)
???????????????????????????select?t.AsType()).ToList();
????????return?entityTypeCache;
????}
????private?IEnumerable?GetReferencingAssemblies()
???? {
????????var?assemblies?=?new?List();
????????var?dependencies?=?DependencyContext.Default.RuntimeLibraries;
????????foreach?(var?library?in?dependencies)
????????{
????????????var?assembly?=?Assembly.Load(new?AssemblyName(library.Name));
????????????assemblies.Add(assembly);
????????}
????????return?assemblies;
????}
}

上一小节我们查找到了继承基类的所有实体,那么现在我们就将全局过滤器应用到实体。
第一步首先,获取租户 id 和前面对应的实现,并注入到上下文构造函数中:
public?class?EFContext?:?DbContext
{
????public?DbSet?Employees;
????public?DbSet?Departments;
????protected?override?void?OnModelCreating(ModelBuilder?modelBuilder)
????{
????????modelBuilder.ApplyConfiguration(new?EmployeeConfiguration());
????????modelBuilder.ApplyConfiguration(new?DepartmentConfiguration());
????????base.OnModelCreating(modelBuilder);
????}
????private?int?tId;
????private?IEntityTypeProvider?entityTypeProvider;
????public?EFContext(DbContextOptions?options,ITenantProvider?tenantProvider,IEntityTypeProvider?entityTypeProvider ):base(options)
????{
????????tId?=?tenantProvider.GetTId();
????????this.entityTypeProvider?=?entityTypeProvider;
????}
}
public?void?GlobalQuery?(ModelBuilder?builder)?where?T?:BaseModel
{
????builder.Entity ().HasQueryFilter(e?=>?e.TId?==?tId?&&?!e.IsDelete);
}
static?readonly?MethodInfo?GlobalQueryMethod?=?typeof(EFContext).GetMethods(BindingFlags.Public?|?BindingFlags.Instance).Single(p=>p.IsGenericMethod?&&?p.Name==?"GlobalQuery");
protected?override?void?OnModelCreating(ModelBuilder?modelBuilder)
{
????modelBuilder.ApplyConfiguration(new?EmployeeConfiguration());
????modelBuilder.ApplyConfiguration(new?DepartmentConfiguration());
????foreach?(var?item?in?entityTypeProvider.GetTypes())
????{
????????var?method?=?GlobalQueryMethod.MakeGenericMethod(item);
????????method.Invoke(this,?new?object[]?{?modelBuilder?});
????}
????base.OnModelCreating(modelBuilder);
}

这篇文章这是简单的实现了多租户和软删除,队医业务场景更加复杂的项目,我们需要利用一些特殊方法来实现全局查询过滤器。上面所说的方法只是其中一种,不排除由更好的方法。
【END】

?热 文?推 荐?
?GitLab地域封锁,总监愤而辞职!苹果产品路线图曝光;CAT 0.1.0发布|极客头条
?下个十年的 C 位:物联网趋势大剧透
?1 时 3 分 59 秒破 1000 亿!天猫双 11 再创新记录!背后的黑科技大揭秘
点击阅读原文参与开发者大调查,好礼送不停!

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 中共中央召开党外人士座谈会 7904717
- 2 贪污超11亿!白天辉被执行死刑 7808860
- 3 日本强震 高市早苗神色慌张一路小跑 7714220
- 4 全国首艘氢电拖轮作业亮点多 7617331
- 5 王毅:是可忍孰不可忍 7521602
- 6 河北沧州杀妻案男方被判死刑 7423602
- 7 《大明王朝1566》逆袭成国产剧天花板 7331416
- 8 水银体温计将于2026年禁产 7235806
- 9 日本发生7.5级强震后 高市早苗发声 7143095
- 10 “人造太阳”何以照进现实 7045771







CSDN
