EF 基于 Interceptor 实现软删除

本文介绍了如何在EFCore7.0中使用Interceptor实现软删除功能,通过ISoftDeleteEntity接口和SoftDeleteInterceptor拦截器,实现在删除操作时标记为软删除状态而非物理删除。同时提供了DbContext的配置和使用示例。
摘要由CSDN通过智能技术生成

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();

输出结果如下:

output

可以看到查询会自动带一个 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值