One package — everything included:
dotnet add package EfCoreKitInherit from EfCoreDbContext<T> instead of DbContext:
using EfCoreKit.Core.Context;
public class AppDbContext : EfCoreDbContext<AppDbContext>
{
public DbSet<Customer> Customers => Set<Customer>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>(); // required only when fullLog: true
public AppDbContext(DbContextOptions<AppDbContext> options, EfCoreOptions efOptions)
: base(options, efOptions) { }
}Tip: Inheriting from
EfCoreDbContext<T>is optional — the interceptors and extensions work with anyDbContext. The base class wires up interceptors and global query filters automatically.
// Pick only the features you need — enable each option once
builder.Services.AddEfCoreExtensions<AppDbContext>(
options => options.UseSqlServer(connectionString),
kit => kit
.EnableSoftDelete()
.EnableAuditTrail() // basic: stamps CreatedAt/By, UpdatedAt/By
// .EnableAuditTrail(fullLog: true) // alternative: also writes field-level AuditLog rows
.UseUserProvider<HttpContextUserProvider>()
.LogSlowQueries(TimeSpan.FromSeconds(1)));Each Enable*() call is opt-in — only the features you enable are active.
AddEfCoreExtensions also registers:
IRepository<T>andIReadRepository<T>→ backed byRepository<T>IUnitOfWork→ backed byUnitOfWork<TContext>
The easiest path is to inherit a base class — they wire up the interface boilerplate for you:
// Plain entity with int PK
public class Product : BaseEntity { }
// Audited (CreatedAt/By, UpdatedAt/By) with Guid PK
public class Order : AuditableEntity<Guid> { }
// Soft-deletable + audited, int PK
public class Customer : SoftDeletableEntity { }
// Full — soft-delete + audit + row version
public class Invoice : FullEntity { }You can also implement interfaces directly if you prefer to control your own hierarchy:
public class Customer : IAuditable, ISoftDeletable
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
}Use the configuration base classes to auto-apply standard EF Core mappings:
public class CustomerConfiguration : SoftDeletableEntityConfiguration<Customer>
{
protected override void ConfigureEntity(EntityTypeBuilder<Customer> builder)
{
builder.Property(c => c.Name).HasMaxLength(200).IsRequired();
builder.HasIndex(c => c.Email).IsUnique();
}
}| Base class | Auto-configures |
|---|---|
BaseEntityConfiguration<T, TKey> |
Primary key (HasKey(e => e.Id)) |
AuditableEntityConfiguration<T, TKey> |
Key + audit columns + index on CreatedAt |
SoftDeletableEntityConfiguration<T, TKey> |
Audit + IsDeleted default + composite index on (IsDeleted, CreatedAt) |
EfCoreKit needs to know who the current user is for audit fields. Implement IUserProvider:
public class HttpContextUserProvider : IUserProvider
{
private readonly IHttpContextAccessor _accessor;
public HttpContextUserProvider(IHttpContextAccessor accessor) => _accessor = accessor;
public string? GetCurrentUserId()
=> _accessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
public string? GetCurrentUserName()
=> _accessor.HttpContext?.User?.Identity?.Name;
}Once configured, EfCoreKit handles the following via EF Core interceptors:
| Feature | What happens | When |
|---|---|---|
| Audit Trail | Sets CreatedAt/CreatedBy on insert, UpdatedAt/UpdatedBy on update |
Every SaveChanges / SaveChangesAsync |
| Soft Delete | Converts DELETE to UPDATE SET IsDeleted = true |
When deleting an ISoftDeletable entity |
| Query Filters | Hides soft-deleted rows | Every LINQ query |
| Slow Query Logging | Logs a warning for queries exceeding the threshold | After each database command |
| Concurrency | Throws ConcurrencyConflictException on stale row version conflicts |
Every SaveChanges / SaveChangesAsync |
- Base Entities — Entity class hierarchy and configuration bases
- Soft Delete — Lifecycle methods, restoring records, cascade delete
- Audit Trail — Timestamps, user tracking, field-level AuditLog
- Repository & Unit of Work — Generic repository and transaction management
- Specification Pattern — Composable, reusable query logic
- Pagination — Offset and keyset/cursor pagination
- Dynamic Filters — Runtime filter arrays
- Query Helpers — WhereIf, OrderByDynamic, DbSet extensions
- DbContext Utilities — Transactions, DetachAll, TruncateAsync
- Exceptions — All exception types, when they're thrown, what to catch
Next: Base Entities →