EfEx.SoftDelete 1.0.0

dotnet add package EfEx.SoftDelete --version 1.0.0
                    
NuGet\Install-Package EfEx.SoftDelete -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EfEx.SoftDelete" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EfEx.SoftDelete" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EfEx.SoftDelete" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EfEx.SoftDelete --version 1.0.0
                    
#r "nuget: EfEx.SoftDelete, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EfEx.SoftDelete@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EfEx.SoftDelete&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EfEx.SoftDelete&version=1.0.0
                    
Install as a Cake Tool

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 ISoftDeletetable on 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 implementing ISoftDeletetable

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.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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