C# EF Core全局查询过滤器方法 C#如何实现软删除

来源:这里教程网 时间:2026-02-21 17:40:29 作者:

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
,所有查询默认看不到已删数据——这两条纪律一旦松动,软删除就形同虚设。

相关推荐

热文推荐