Soft Deletes
Infrastructure
ISoftDelete.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
public interface ISoftDelete
{
public bool IsDeleted { get; set; }
public DateTimeOffset? DeletedAt { get; set; }
public void Undo()
{
IsDeleted = false;
DeletedAt = null;
}
}
SoftDeleteInterceptor.cs
public class SoftDeleteInterceptor : SaveChangesInterceptor
{
private readonly IClock _clock;
public SoftDeleteInterceptor(IClock clock)
{ _clock = clock;
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = new CancellationToken())
{
if (eventData.Context is null) return ValueTask.FromResult(result);
foreach (var entry in eventData.Context.ChangeTracker.Entries())
{
if (entry is not {State: EntityState.Deleted, Entity: ISoftDelete delete}) continue;
entry.State = EntityState.Modified;
delete.IsDeleted = true;
delete.DeletedAt = _clock.GetCurrentInstant();
}
return ValueTask.FromResult(result);
}
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
if (eventData.Context is null) return result;
foreach (var entry in eventData.Context.ChangeTracker.Entries())
{
if (entry is not {State: EntityState.Deleted, Entity: ISoftDelete delete}) continue;
entry.State = EntityState.Modified;
delete.IsDeleted = true;
delete.DeletedAt = _clock.GetCurrentInstant();
}
return result;
}
}
Configuring Entities
UserEntityConfiguration.cs
public class UserEntityConfiguration : EntityMappingConfiguration<User>
{
public override void Map(EntityTypeBuilder<User> builder)
{
builder
.HasQueryFilter(p => p.IsDeleted == false);
}
}