EF 基于 Interceptor 实现软删除
Intro
efcore 从 5.0 版本开始支持了 Interceptor,并在 7.0 中增强了 interceptor 的功能,利用 interceptor 我们可以更加便捷更加无侵入的实现一些逻辑,比如说我们有时候数据库表中的数据会只想做软删除,不想直接从数据表中删除,此时我们也可以借助 interceptor 来实现这样的逻辑,可以参考下面的示例
Sample
SoftDeleteEntity
为了比较方便的区分 entity 是否要软删除,这里定义了一个接口 ISoftDeleteEntityWithDeleted
public interface ISoftDeleteEntity
{
}
public interface ISoftDeleteEntityWithDeleted : ISoftDeleteEntity
{
bool IsDeleted { get; set; }
}
实现的方式多样,你也可以用 attribute 来区分
SoftDeleteInterceptor
首先我们实现一个 SoftDeleteInteceptor
, 继承于 SaveChangesInterceptor
通过 override
更新之前的方法 SavingChanges
/SavingChangesAsync
在更新之前看一下被删除的对象是不是要软删除的,如果要软删除则将对象的 IsDeleted
设置为 true
并将状态改成 Modified
,以此来实现软删除的效果
实现代码如下:
public sealed class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
OnSavingChanges(eventData);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result,
CancellationToken cancellationToken = new CancellationToken())
{
OnSavingChanges(eventData);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
private static void OnSavingChanges(DbContextEventData eventData)
{
ArgumentNullException.ThrowIfNull(eventData.Context);
eventData.Context.ChangeTracker.DetectChanges();
foreach (var entityEntry in eventData.Context.ChangeTracker.Entries())
{
if (entityEntry is { State: EntityState.Deleted, Entity: ISoftDeleteEntityWithDeleted softDeleteEntity })
{
softDeleteEntity.IsDeleted = true;
entityEntry.State = EntityState.Modified;
}
}
}
}
Usage sample
DbContext 代码如下:
public class SoftDeleteSampleContext : DbContext
{
public SoftDeleteSampleContext(DbContextOptions<SoftDeleteSampleContext> options) : base(options)
{
}
public virtual DbSet<SoftDeleteEntity> TestEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// apply global query filter
modelBuilder.Entity<SoftDeleteEntity>().HasQueryFilter(x => x.IsDeleted == false);
base.OnModelCreating(modelBuilder);
}
}
public class SoftDeleteEntity : ISoftDeleteEntityWithDeleted
{
public int Id { get; set; }
public string Name { get; set; } = "test";
public bool IsDeleted { get; set; }
}
为了更方便的查询,我们可以给要软删除的添加一个 global query filter,这样我们每次查询的时候就可以自动过滤已经被软删除的对象了
使用示例代码如下:
var services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
});
services.AddDbContext<SoftDeleteSampleContext>(options =>
{
options
.UseSqlite("Data Source=SoftDeleteTest.db")
.AddInterceptors(new SoftDeleteInterceptor());
});
using var serviceProvider = services.BuildServiceProvider();
var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<SoftDeleteSampleContext>();
// initialize
context.Database.EnsureCreated();
context.TestEntities.IgnoreQueryFilters().ExecuteDelete();
context.SaveChanges();
// add test data
context.TestEntities.Add(new SoftDeleteEntity()
{
Id = 1,
Name = "test"
});
context.SaveChanges();
// remove test
var testEntity = context.TestEntities.Find(1);
ArgumentNullException.ThrowIfNull(testEntity);
context.TestEntities.Remove(testEntity);
context.SaveChanges();
// query
var entities = context.TestEntities.AsNoTracking().ToArray();
Console.WriteLine(entities.ToJson());
// query without query filter
entities = context.TestEntities.AsNoTracking().IgnoreQueryFilters().ToArray();
Console.WriteLine(entities.ToJson());
// cleanup test db
context.Database.EnsureDeleted();
输出结果如下:
可以看到查询会自动带一个 IsDeleted
的过滤条件,这就是前面的 query filter 的作用,而且我们的 remove/delete 对应的 SQL 语句也变成了 Update
而非 Delete
More
目前出于简单考虑直接在 property 中声明了一个 IsDeleted
字段,大家可以思考下如何有更好的扩展性哈
References
https://learn.microsoft.com/ef/core/logging-events-diagnostics/interceptors?WT.mc_id=DT-MVP-5004222
https://learn.microsoft.com/en-us/ef/core/querying/filters?WT.mc_id=DT-MVP-5004222
https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/ff28686bbb6b50d2d7564238082760a2c38bdad4/src/WeihanLi.EntityFramework/Interceptors/SoftDeleteInterceptor.cs
https://github.com/WeihanLi/WeihanLi.EntityFramework/blob/dev/samples/WeihanLi.EntityFramework.Sample/Program.cs