EFCore.SoftAudit 1.2.0

dotnet add package EFCore.SoftAudit --version 1.2.0
                    
NuGet\Install-Package EFCore.SoftAudit -Version 1.2.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="EFCore.SoftAudit" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EFCore.SoftAudit" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="EFCore.SoftAudit" />
                    
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 EFCore.SoftAudit --version 1.2.0
                    
#r "nuget: EFCore.SoftAudit, 1.2.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 EFCore.SoftAudit@1.2.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=EFCore.SoftAudit&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=EFCore.SoftAudit&version=1.2.0
                    
Install as a Cake Tool

EFCore.SoftAudit

CI

A lightweight extension for Entity Framework Core that adds audit fields and soft delete support with minimal setup.

Instead of wiring up SaveChanges interceptors and global query filters by hand, inherit from AuditableDbContext, implement two marker interfaces on your entities, and register the context via AddSoftAudit.

Features

  • Automatic audit fields on create and update (CreatedAt, CreatedBy, UpdatedAt, UpdatedBy)
  • Soft deleteRemove() sets IsDeleted = true instead of deleting the row
  • Global query filter — soft-deleted entities are excluded from queries by default
  • RestoreRestore() and RestoreRange() undo a soft delete and re-expose the entity to queries
  • Fluent query extensionsWithDeleted() and OnlyDeleted() on any IQueryable<T> where T : ISoftDeletable
  • Pluggable user and time providers via ICurrentUserProvider and ITimeProvider
  • Configurable claim typeHttpCurrentUserProvider reads any claim you choose (default: ClaimTypes.NameIdentifier)
  • UTC timestamps for all audit and delete fields

Requirements

  • .NET 8.0
  • Entity Framework Core 8.0

Project structure

EFCore.SoftAudit/
├── EFCore.SoftAudit.sln
├── EFCore.SoftAudit.csproj          # Library
├── AuditableDbContext.cs
├── HttpCurrentUserProvider.cs
├── SystemTimeProvider.cs
├── ServiceCollectionExtensions.cs
├── SoftAuditOptions.cs
├── SoftDeleteQueryableExtensions.cs
├── Interfaces/
│   ├── IAuditable.cs
│   ├── ISoftDeletable.cs
│   ├── ICurrentUserProvider.cs
│   └── ITimeProvider.cs
├── samples/
│   └── SampleApi/                   # Demo ASP.NET Core API
└── tests/
    └── EFCore.SoftAudit.Tests/      # Unit tests (xUnit)

Quick start

1. Install the package

dotnet add package EFCore.SoftAudit

Or manually in your .csproj:

<PackageReference Include="EFCore.SoftAudit" Version="1.2.0" />

2. Implement interfaces on your entity

using EFCore.SoftAudit.Interfaces;

public class Order : IAuditable, ISoftDeletable
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public int Quantity { get; set; }

    // IAuditable
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public string? CreatedBy { get; set; }
    public string? UpdatedBy { get; set; }

    // ISoftDeletable
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
    public string? DeletedBy { get; set; }
}

You can implement either interface independently — an entity with only ISoftDeletable gets soft delete without audit fields; an entity with only IAuditable gets audit fields with normal (hard) delete.

3. Create a DbContext

using EFCore.SoftAudit;
using EFCore.SoftAudit.Interfaces;
using Microsoft.EntityFrameworkCore;

public class AppDbContext(
    DbContextOptions<AppDbContext> options,
    ICurrentUserProvider? currentUserProvider,
    ITimeProvider? timeProvider)
    : AuditableDbContext(options, currentUserProvider, timeProvider)
{
    public DbSet<Order> Orders => Set<Order>();
}

4. Register in DI

using EFCore.SoftAudit;

// Default — reads ClaimTypes.NameIdentifier from the current HTTP context
builder.Services.AddSoftAudit<AppDbContext>(options =>
    options.UseSqlite("Data Source=app.db"));

// Custom claim type (e.g. "sub" for OAuth2 / OpenID Connect)
builder.Services.AddSoftAudit<AppDbContext>(
    options => options.UseSqlite("Data Source=app.db"),
    audit => audit.UserClaimType = "sub");

AddSoftAudit registers:

  • IHttpContextAccessor
  • ICurrentUserProviderHttpCurrentUserProvider
  • ITimeProviderSystemTimeProvider
  • your DbContext

Custom providers (console apps, tests, workers)

Register your own implementations before AddDbContext:

services.AddSingleton<ITimeProvider, FakeTimeProvider>();
services.AddSingleton<ICurrentUserProvider, FakeUserProvider>();
services.AddDbContext<AppDbContext>(options => ...);

Or pass null providers to AuditableDbContext — timestamps fall back to DateTime.UtcNow, user fields remain null.

How it works

Audit fields (IAuditable)

Event Fields set
Insert CreatedAt, CreatedBy
Update UpdatedAt, UpdatedBy

CreatedBy / UpdatedBy come from ICurrentUserProvider.GetCurrentUserId(). In ASP.NET Core apps this is populated from the configured claim via HttpCurrentUserProvider.

Soft delete (ISoftDeletable)

Calling DbSet.Remove(entity) does not issue a SQL DELETE. Instead, the entity is marked as modified with:

  • IsDeleted = true
  • DeletedAt = <UTC now>
  • DeletedBy = <current user>

A global query filter (IsDeleted == false) is applied automatically to every entity that implements ISoftDeletable, so deleted rows are hidden from normal queries.

Querying soft-deleted entities

Use the fluent extensions from EFCore.SoftAudit:

// Include deleted alongside active records
var allOrders = await db.Orders.WithDeleted().ToListAsync();

// Return only deleted records
var deletedOrders = await db.Orders.OnlyDeleted().ToListAsync();

Note: WithDeleted() calls IgnoreQueryFilters() internally, which removes all global query filters on the entity type — not just the soft-delete filter. If you have additional filters (e.g. multi-tenancy), they will also be bypassed.

Restoring soft-deleted entities

// Restore a single entity
db.Restore(order);
await db.SaveChangesAsync();

// Restore multiple entities at once
db.RestoreRange(orders);
await db.SaveChangesAsync();

After restore, IsDeleted, DeletedAt, and DeletedBy are cleared. If the entity also implements IAuditable, UpdatedAt and UpdatedBy are stamped on the next SaveChanges.

Prefer FirstOrDefaultAsync over FindAsync when loading by id — FindAsync may return soft-deleted entities already tracked by the context.

Limitations

  • ExecuteDelete() and ExecuteUpdate() bypass SaveChanges — soft delete does not apply
  • UpdatedAt is set on any Modified entity, even without business property changes
  • CreatedAt is always overwritten on insert

Sample API

The samples/SampleApi project is a minimal REST API demonstrating the library with SQLite.

dotnet run --project samples/SampleApi/SampleApi.csproj

Swagger UI is available at https://localhost:7009/swagger in Development.

Method Endpoint Description
POST /orders Create an order
GET /orders List active orders
DELETE /orders/{id} Soft-delete an order

Running tests

dotnet test

Tests use an in-memory database and cover audit fields, soft delete, restore, sync/async SaveChanges, query extensions, and custom user/time providers.

Building the solution

dotnet build EFCore.SoftAudit.sln

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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.2.0 32 6/5/2026
1.1.1 94 6/1/2026
1.1.0 106 5/31/2026 1.1.0 is deprecated.
1.0.0 104 5/31/2026 1.0.0 is deprecated.