EF Core软删除通过全局查询过滤器自动添加IsDeleted==false条件并重写SaveChanges将Deleted状态改为Modified来实现。需定义ISoftDelete接口和BaseEntity基类,批量配置过滤器,慎用IgnoreQueryFilters()。

EF Core 实现软删除,核心是配合 全局查询过滤器(Global Query Filters) 自动对所有查询添加条件(如 IsDeleted == false),同时在删除操作中不真正删数据,而是标记为已删除。关键在于配置一致、避免绕过、注意导航属性和显式加载的兼容性。
定义软删除接口和实体基类
统一管理软删除字段,便于复用和约束:
- 定义接口
ISoftDelete,含IsDeleted和可选的DeletedAt属性 - 让需要软删除的实体继承一个基类(如
BaseEntity),实现该接口 - 确保数据库字段对应(如
IsDeleted类型为bit或boolean)
示例:
public interface ISoftDelete{
bool IsDeleted { get; set; }
DateTime? DeletedAt { get; set; }
}
public abstract class BaseEntity : ISoftDelete
{
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
}
在 DbContext 中配置全局查询过滤器
在 OnModelCreating 中为每个支持软删除的实体启用过滤器:
- 使用
modelBuilder.Entity().HasQueryFilter(x => !x.IsDeleted) - 推荐用泛型方式批量注册,避免漏配;可用反射扫描继承自
BaseEntity的类型 - 过滤器会在所有 LINQ 查询(包括
Include导航属性)中自动生效
示例(手动配置):
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.Entity
modelBuilder.Entity
}
重写 Delete 方法实现软删除逻辑
EF Core 默认调用 Remove() 会触发物理删除,需改为更新状态:
- 重写
SaveChanges/SaveChangesAsync,扫描待删除实体,将其标记为IsDeleted = true,并取消物理删除操作 - 将实体状态从
Deleted改为Modified,仅更新软删除字段 - 注意:需排除已真正删除(如系统日志等不需要软删)的实体类型
示例片段:
public override int SaveChanges(bool acceptAllChangesOnSuccess){
foreach (var entry in ChangeTracker.Entries
{
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
entry.CurrentValues["DeletedAt"] = DateTime.UtcNow;
}
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
绕过过滤器的场景与安全处理
某些业务需要查“已删除”数据(如回收站),EF Core 提供 IgnoreQueryFilters():
- 仅限可信上下文使用,例如后台管理页,避免暴露给前端随意调用
- 注意:它会跳过所有过滤器(不止软删除),慎用于包含多过滤器的模型
- 也可用
AsNoTrackingWithIdentityResolution()配合,但不推荐替代过滤器绕过
示例:
var deletedUsers = context.Users.IgnoreQueryFilters()
.Where(u => u.IsDeleted)
.ToList();
基本上就这些。全局过滤器 + 状态拦截是 EF Core 软删除最稳妥的组合,既保持代码简洁,又避免业务层遗漏判断。注意测试导航查询、显式加载和批量操作是否符合预期。










