Introduction
In this article, we will learn about Soft Deletes using EF Core Interceptors.
Before we start, please take a look at my last article on Entity Framework.
Now, let's get started.
Soft delete using EF Core interceptors is an excellent pattern to automatically filter out "deleted" entities without physically removing them from the database. You mark entities as deleted by setting a flag (e.g., IsDeleted), and the interceptor ensures queries ignore those flagged records.
What is Soft Delete?
- Instead of physically deleting a row from the database, you mark it as deleted (e.g., with a Boolean IsDeleted column or a DeletedAt timestamp).
- This allows you to keep historical data and recover deleted records if needed.
- You want your queries to exclude soft-deleted entities automatically.
Why Use EF Core Interceptors?
- Interceptors let you hook into EF Core’s SaveChanges pipeline.
- You can intercept delete operations and convert them to update operations that set IsDeleted = true.
- You can globally apply query filters to exclude soft-deleted records.
Step-by-Step Example
1. Define a Soft Delete Interface
First, define an interface for soft-deletable entities.
public interface ISoftDelete
{
bool IsDeleted { get; set; }
}
Entities that support soft delete implement this.
public class Product : ISoftDelete
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
2. Create a SaveChangesInterceptor for Soft Delete
You want to intercept delete operations and convert them into an update setting IsDeleted = true.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
public class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
SoftDeleteEntities(eventData.Context);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
SoftDeleteEntities(eventData.Context);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
private void SoftDeleteEntities(DbContext? context)
{
if (context == null) return;
var entries = context.ChangeTracker.Entries<ISoftDelete>()
.Where(e => e.State == EntityState.Deleted);
foreach (var entry in entries)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
}
}
}
3. Register the Interceptor in DbContext
In your DbContext class, register the interceptor.
public class AppDbContext : DbContext
{
private readonly SoftDeleteInterceptor _softDeleteInterceptor;
public AppDbContext(DbContextOptions<AppDbContext> options, SoftDeleteInterceptor softDeleteInterceptor)
: base(options)
{
_softDeleteInterceptor = softDeleteInterceptor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(_softDeleteInterceptor);
}
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply global query filter for soft delete
modelBuilder.Entity<Product>().HasQueryFilter(p => !p.IsDeleted);
// Repeat for other soft delete entities or use a generic approach
}
}
4. Global Query Filter for Soft Deleted Entities
Note: the global query filter.HasQueryFilter(p => !p.IsDeleted); which automatically excludes soft-deleted entities from queries.
Summary
- Entities implement ISoftDelete with the IsDeleted property.
- Interceptor converts deletes into updates that mark entities as soft deleted (IsDeleted = true).
- Global query filters exclude soft-deleted entities from queries.
- Your app "soft deletes" transparently.
Conclusion
In this article, I have tried to cover Soft Deletes using EF Core Interceptors.