EfEx.SoftDelete
1.0.0
dotnet add package EfEx.SoftDelete --version 1.0.0
NuGet\Install-Package EfEx.SoftDelete -Version 1.0.0
<PackageReference Include="EfEx.SoftDelete" Version="1.0.0" />
<PackageVersion Include="EfEx.SoftDelete" Version="1.0.0" />
<PackageReference Include="EfEx.SoftDelete" />
paket add EfEx.SoftDelete --version 1.0.0
#r "nuget: EfEx.SoftDelete, 1.0.0"
#:package EfEx.SoftDelete@1.0.0
#addin nuget:?package=EfEx.SoftDelete&version=1.0.0
#tool nuget:?package=EfEx.SoftDelete&version=1.0.0
EfCore.SoftDelete
A lightweight Entity Framework Core extension that provides automatic soft delete functionality. This package automatically converts hard deletes to soft deletes and filters out deleted entities from queries.
Features
- ✅ Automatic Soft Delete Conversion: Intercepts delete operations and converts them to soft deletes
- ✅ Query Filtering: Automatically excludes soft-deleted entities from queries
- ✅ Simple Interface: Just implement
ISoftDeletetableon your entities - ✅ Async Support: Works seamlessly with both synchronous and asynchronous operations
- ✅ Zero Configuration: Minimal setup required
- ✅ EF Core 9.0 Compatible: Built for the latest Entity Framework Core
Installation
Install the package via NuGet:
dotnet add package EfCore.SoftDelete
Or via Package Manager Console:
Install-Package EfCore.SoftDelete
Quick Start
1. Implement the Interface
Make your entity implement ISoftDeletetable:
using EfEx.SoftDelete.Models;
public class User : ISoftDeletetable
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
// Soft delete properties
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
}
2. Configure DbContext
Add the interceptor and enable soft delete in your DbContext:
using Microsoft.EntityFrameworkCore;
using EfEx.SoftDelete.Interceptors;
using EfEx.SoftDelete.Extensions;
public class ApplicationDbContext : DbContext
{
public DbSet<User> Users { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("your-connection-string")
.AddInterceptors(new SoftDeleteInterceptor());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Enable automatic query filtering for soft-deleted entities
modelBuilder.EnableSoftDelete();
}
}
3. Use It!
Now when you delete an entity, it will be soft-deleted automatically:
// This will soft-delete the user instead of hard-deleting
var user = await context.Users.FindAsync(1);
context.Users.Remove(user);
await context.SaveChangesAsync();
// The user still exists in the database with IsDeleted = true
// But it won't appear in normal queries
var activeUsers = await context.Users.ToListAsync(); // Won't include deleted users
Usage Examples
Basic Soft Delete
// Delete an entity
var user = await context.Users.FindAsync(1);
context.Users.Remove(user);
await context.SaveChangesAsync();
// The entity is now soft-deleted:
// - IsDeleted = true
// - DeletedAt = DateTime.UtcNow
// - Still exists in database
Querying Active Entities
Soft-deleted entities are automatically excluded from queries:
// Only returns users where IsDeleted = false
var activeUsers = await context.Users.ToListAsync();
// FindAsync also respects the filter
var user = await context.Users.FindAsync(1); // Returns null if deleted
Including Deleted Entities
To query soft-deleted entities, use IgnoreQueryFilters():
// Get all users including deleted ones
var allUsers = await context.Users
.IgnoreQueryFilters()
.ToListAsync();
// Find a specific user even if deleted
var deletedUser = await context.Users
.IgnoreQueryFilters()
.FirstOrDefaultAsync(u => u.Id == 1);
Setting DeletedBy
You can set the DeletedBy property before deleting:
var user = await context.Users.FindAsync(1);
user.DeletedBy = "admin@example.com";
context.Users.Remove(user);
await context.SaveChangesAsync();
Restoring Soft-Deleted Entities
To restore a soft-deleted entity:
var deletedUser = await context.Users
.IgnoreQueryFilters()
.FirstOrDefaultAsync(u => u.Id == 1 && u.IsDeleted);
if (deletedUser != null)
{
deletedUser.IsDeleted = false;
deletedUser.DeletedAt = null;
deletedUser.DeletedBy = null;
await context.SaveChangesAsync();
}
Hard Delete (Permanent Delete)
If you need to permanently delete an entity, you can bypass the interceptor:
var user = await context.Users
.IgnoreQueryFilters()
.FirstOrDefaultAsync(u => u.Id == 1);
if (user != null)
{
// Manually set IsDeleted and save, then remove
// Or use ExecuteDelete for EF Core 7+
await context.Users
.Where(u => u.Id == 1)
.ExecuteDeleteAsync();
}
API Reference
ISoftDeletetable Interface
public interface ISoftDeletetable
{
bool IsDeleted { get; set; }
DateTime? DeletedAt { get; set; }
string? DeletedBy { get; set; }
}
SoftDeleteInterceptor
Intercepts SaveChanges and SaveChangesAsync operations to convert hard deletes to soft deletes.
Usage:
optionsBuilder.AddInterceptors(new SoftDeleteInterceptor());
SoftDeleteExtensions
Extension methods for ModelBuilder to enable query filtering.
Methods:
EnableSoftDelete(): Applies query filters to all entities implementingISoftDeletetable
Usage:
modelBuilder.EnableSoftDelete();
Advanced Scenarios
Multiple DbContexts
If you have multiple DbContext classes, add the interceptor and extension to each:
public class UserDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new SoftDeleteInterceptor());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.EnableSoftDelete();
}
}
Partial Soft Delete
You can have some entities with soft delete and others without:
public class User : ISoftDeletetable
{
// Has soft delete
}
public class LogEntry
{
// No soft delete - will be hard deleted
}
Custom DeletedBy Logic
You can set DeletedBy based on your authentication context:
public class ApplicationDbContext : DbContext
{
private readonly ICurrentUserService _currentUser;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ICurrentUserService currentUser)
: base(options)
{
_currentUser = currentUser;
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Set DeletedBy before save
var deletedEntries = ChangeTracker.Entries<ISoftDeletetable>()
.Where(e => e.State == EntityState.Deleted);
foreach (var entry in deletedEntries)
{
entry.Entity.DeletedBy = _currentUser.UserId;
}
return await base.SaveChangesAsync(cancellationToken);
}
}
Migration
When adding soft delete to existing entities, create a migration:
dotnet ef migrations Add AddSoftDeleteToUsers
The migration will add the soft delete columns:
migrationBuilder.AddColumn<bool>(
name: "IsDeleted",
table: "Users",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "DeletedAt",
table: "Users",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "DeletedBy",
table: "Users",
nullable: true);
Requirements
- .NET 9.0 or later
- Entity Framework Core 9.0 or later
License
This project is licensed under the MIT License.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues, questions, or contributions, please visit the GitHub repository.
Related Packages
- EfCore.Audit - Entity Framework Core audit trail package
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 275 | 11/14/2025 |