在C#项目中使用EF Core进行数据持久化时,软删除是比物理删除更稳妥的方案,它通过在表中添加标记字段来标识数据是否被删除,而不是直接从数据库移除记录。结合EF Core的全局查询过滤器,可以让所有查询自动过滤掉已标记为删除的数据,减少重复编写过滤条件的麻烦。

软删除实现前的准备
首先需要在实体类中添加软删除标记字段,通常我们使用布尔类型的字段来标识是否删除,也可以根据需求使用时间戳类型记录删除时间。以下是最基础的实体类定义示例:
// 定义基础实体接口,所有需要软删除的实体实现该接口
public interface ISoftDelete
{
// 是否删除的标记,true表示已删除,false表示未删除
bool IsDeleted { get; set; }
}
// 示例实体类,实现ISoftDelete接口
public class User : ISoftDelete
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
// 实现接口的软删除标记字段
public bool IsDeleted { get; set; }
}
配置全局查询过滤器
接下来需要在DbContext中配置全局查询过滤器,让EF Core在生成查询语句时自动添加过滤条件。我们在OnModelCreating方法中为所有实现ISoftDelete接口的实体配置过滤器:
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 遍历所有实现ISoftDelete接口的实体类型,配置全局查询过滤器
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
{
// 配置过滤条件:IsDeleted == false
modelBuilder.Entity(entityType.ClrType)
.HasQueryFilter(e => EF.Property<bool>(e, "IsDeleted") == false);
}
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// 配置数据库连接,这里使用SQL Server示例
optionsBuilder.UseSqlServer("Server=.;Database=TestDb;Trusted_Connection=True;");
}
}
软删除操作的实现
软删除不需要直接调用Remove方法,而是修改实体的IsDeleted字段为true,然后保存更改即可。以下是软删除的示例代码:
public class UserService
{
private readonly AppDbContext _dbContext;
public UserService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
// 软删除用户
public async Task SoftDeleteUserAsync(int userId)
{
var user = await _dbContext.Users.FindAsync(userId);
if (user == null)
{
throw new Exception("用户不存在");
}
// 修改删除标记为true
user.IsDeleted = true;
// 保存更改
await _dbContext.SaveChangesAsync();
}
}
查询操作验证
配置了全局查询过滤器后,所有针对User实体的查询都会自动过滤掉IsDeleted为true的记录,无需手动添加条件。以下是查询示例:
public class UserService
{
private readonly AppDbContext _dbContext;
public UserService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
// 查询所有未删除的用户
public async Task<List<User>> GetAllUsersAsync()
{
// 这里不需要手动添加Where(u => !u.IsDeleted)条件
// 全局查询过滤器会自动生效
return await _dbContext.Users.ToListAsync();
}
}
恢复删除数据
如果需要恢复已经软删除的数据,只需要将对应实体的IsDeleted字段改回false,然后保存更改即可:
public class UserService
{
private readonly AppDbContext _dbContext;
public UserService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
// 恢复软删除的用户
public async Task RestoreUserAsync(int userId)
{
// 忽略全局查询过滤器,查询所有包括已删除的用户
var user = await _dbContext.Users
.IgnoreQueryFilters()
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
throw new Exception("用户不存在");
}
// 将删除标记改回false
user.IsDeleted = false;
await _dbContext.SaveChangesAsync();
}
}
注意事项
- 全局查询过滤器只对查询操作生效,新增、修改、删除操作不受影响,删除操作需要手动修改标记字段。
- 如果需要查询包括已删除的数据,可以使用
IgnoreQueryFilters方法忽略全局过滤器。 - 如果实体类有多个查询过滤条件,EF Core会将所有条件通过逻辑与组合,需要注意条件的合理性。
- 导航属性的查询也会自动应用全局查询过滤器,比如查询用户关联的订单时,已删除的订单不会被加载。