EF Core 的 HasQueryFilter
怎么加软删除条件
全局查询过滤器的核心就是
HasQueryFilter,它让 EF Core 在生成 SQL 时自动拼上指定条件。软删除通常靠一个
IsDeleted布尔字段或
DeletedAt可空时间戳,最常用的是前者。
在
OnModelCreating中为实体配置过滤器:
modelBuilder.Entity<Order>()
.HasQueryFilter(x => !x.IsDeleted);
注意:这个表达式必须是可被 EF Core 翻译成 SQL 的纯 LINQ 表达式,不能调用本地方法、不能访问闭包变量(如
var now = DateTime.Now),否则运行时报
InvalidOperationException: The LINQ expression could not be translated。 如果实体有继承关系(比如
BaseEntity含
IsDeleted),可对基类统一配置:
modelBuilder.Entity<baseentity>().HasQueryFilter(x => !x.IsDeleted)</baseentity>多个实体需分别调用
HasQueryFilter,EF Core 不支持“对所有实体自动应用” 过滤器只影响查询(
Find、
Where、
Include等),不影响
SaveChanges或原始 SQL
怎么绕过全局过滤器查已删除数据
有时需要查看或恢复软删除记录,这时得临时禁用过滤器。EF Core 提供了
IgnoreQueryFilters()方法:
context.Orders.IgnoreQueryFilters().Where(x => x.IsDeleted).ToList();
它只作用于当前查询链,不影响其他查询。但要注意:
IgnoreQueryFilters()必须放在查询构建的最前面(比如
context.Xxx.IgnoreQueryFilters().Where(...)),放错位置(如
.Where(...).IgnoreQueryFilters())会失效 它不递归跳过导航属性的过滤器,若
Order包含
Customer,而
Customer也有过滤器,
Include(x => x.Customer)仍会受其约束 若需完全绕过(包括关联实体),得对每个导航属性单独调用
IgnoreQueryFilters(),或改用投影(
Select)避免加载实体
软删除字段类型选 bool
还是 DateTime?
两种都常见,但行为和维护成本不同:
bool IsDeleted:简单直接,索引小,查询快(
!x.IsDeleted可走索引),适合大多数场景;缺点是无法知道删于何时、谁删的
DateTime? DeletedAt:能记录删除时间,便于审计;但
DeletedAt == null的判断在某些数据库(如 SQL Server)可能无法高效利用索引,且默认值需手动设为
null如果后续要加逻辑(如“7天后自动物理删除”),
DateTime?更易扩展;若只是开关式软删,
bool更轻量
无论选哪种,都要确保迁移脚本里该字段允许为空(
DeletedAt)或有默认值(
IsDeleted = false),否则新增实体插入失败。
软删除与 SaveChanges
如何联动
EF Core 不会自动把
Delete操作转为更新,必须手动拦截。推荐在
SaveChanges重写中处理:
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
foreach (var entry in ChangeTracker.Entries<ISoftDelete>()
.Where(e => e.State == EntityState.Deleted))
{
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
其中
ISoftDelete是你定义的接口(含
IsDeleted属性)。关键点: 必须检查
entry.State == EntityState.Deleted,否则修改其他状态的实体会出错 用
entry.CurrentValues["IsDeleted"] = true而非
entry.Entity.IsDeleted = true,避免触发属性 setter 里的业务逻辑(比如日志) 若使用异步
SaveChangesAsync,需重写对应异步版本 物理删除仍应保留(比如管理员强制清理),可通过临时禁用跟踪或标记特殊上下文来区分
真正难的不是加过滤器,而是让团队所有人记住:所有
Delete都不该直接调用
Remove,所有查询默认看不到已删数据——这两条纪律一旦松动,软删除就形同虚设。
