hasqueryfilter必须在onmodelcreating中配置,仅对显式注册的实体生效,支持参数化过滤器(ef core 5+),绕过需用ignorequeryfilters()且须置于查询链开头,软删除字段须加数据库索引。

HasQueryFilter 必须在 OnModelCreating 里配置
全局查询过滤不是运行时动态开关,而是模型构建阶段的硬编码规则。EF Core 启动后,所有对实体的查询(包括 Include 关联、FromSqlRaw 以外的 LINQ 查询)都会自动拼上你写的表达式。漏写或写在别的地方(比如构造函数、OnConfiguring)都无效。
常见错误现象:InvalidOperationException: The query filter cannot be applied because the entity type is not registered in the model. —— 多半是忘了调用 modelBuilder.Entity<t>().HasQueryFilter(...)</t>,或者类型没被 DbSet<t></t> 引用导致未注册。
- 只对已显式注册的实体生效(即有对应
DbSet<t></t>或在OnModelCreating中调用了modelBuilder.Entity<t>()</t>) - 不能用
this或实例字段,只能用常量、静态属性、或参数化表达式(如DateTime.UtcNow可以,DateTime.Now会被编译为常量,不推荐) - 若实体有继承层级(TPH),过滤器会作用于基类,子类自动继承;但需确保基类本身被建模(
modelBuilder.Entity<baseentity>()</baseentity>)
软删除字段命名和类型要统一且可索引
最常用的是 IsDeleted(bool)或 DeletedAt(DateTime?)。选哪个不关键,关键是整个项目保持一致,并在数据库里给该字段加索引——否则每次查询都全表扫描,性能崩得比没加过滤还快。
使用场景:用户删帖后仍需保留数据审计,后台管理需要“恢复”功能,API 默认不返回已删内容但管理员可绕过。
-
IsDeleted更轻量,SQL 条件简单(WHERE IsDeleted = 0),但无法记录删除时间 -
DeletedAt支持按时间范围查历史删除记录,但表达式得写成x => x.DeletedAt == null,注意是null判断,不是!= null - 别用
int类型存状态码(如 0=正常,1=已删),EF Core 不会自动优化这类比较,SQL 生成可能出错
绕过全局过滤必须显式调用 IgnoreQueryFilters
不是所有查询都想走过滤。比如回收站页面、后台导出全量数据、或是迁移脚本同步逻辑——这时候必须手动关掉。没有“默认开启/关闭”的配置项,只有临时绕过这一种方式。
常见错误现象:写了 IgnoreQueryFilters() 却发现没生效,大概率是因为它只对**当前查询链**有效,且必须放在 Where、Include 等操作之前。
- 正确写法:
context.Posts.IgnoreQueryFilters().Where(x => x.IsDeleted).ToList() - 错误写法:
context.Posts.Where(x => x.IsDeleted).IgnoreQueryFilters().ToList()—— 此时过滤器早已参与 SQL 构建,再调也晚了 - 不能用于跟踪查询(
AsNoTracking()是另一回事),但IgnoreQueryFilters()本身不影响跟踪行为 - 如果多个实体都有过滤器,
IgnoreQueryFilters()仅影响调用它的那个DbSet
多租户场景下用 EF Core 5+ 的 Parameterized Query Filters
如果软删除之外还要按租户隔离(比如 TenantId 字段),别把两个条件硬塞进一个 HasQueryFilter。EF Core 5 起支持参数化过滤器,能复用同一个表达式模板,避免每个实体重复写 x => !x.IsDeleted && x.TenantId == currentTenantId。
性能影响:参数化过滤器在首次查询时生成不同 SQL,但后续同租户查询可复用执行计划;而硬编码字符串拼接(如用 $"..." + tenantId)会导致缓存失效、SQL 注入风险。
- 必须配合
DbContext构造时传入租户上下文(如从IHttpContextAccessor拿),并在OnModelCreating中用modelBuilder.Entity<t>().HasQueryFilter(x => x.TenantId == _tenantId)</t>—— 注意_tenantId是实例字段,EF Core 会把它转为参数 - EF Core 3.1 及以前不支持,强行用会报
NotSupportedException: Unable to determine the relationship represented by navigation - 参数值在
SaveChanges时不会自动注入到 INSERT/UPDATE,软删除字段仍需手动赋值(如拦截SavingChanges事件)











