|
| 1 | +# Changelog |
1 | 2 |
|
| 3 | +All notable changes to this project will be documented in this file. |
| 4 | + |
| 5 | +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## [1.0.0] - 2026-04-09 |
| 11 | + |
| 12 | +### Added |
| 13 | + |
| 14 | +#### Base Entity Hierarchy |
| 15 | +- `BaseEntity<TKey>` / `BaseEntity` — strongly-typed primary key base class |
| 16 | +- `AuditableEntity<TKey>` / `AuditableEntity` — adds `CreatedAt`, `CreatedBy`, `UpdatedAt`, `UpdatedBy` |
| 17 | +- `SoftDeletableEntity<TKey>` / `SoftDeletableEntity` — adds `IsDeleted`, `DeletedAt`, `DeletedBy` |
| 18 | +- `FullEntity<TKey>` / `FullEntity` — soft-delete + audit + optimistic concurrency (`RowVersion`) |
| 19 | + |
| 20 | +#### Entity Configuration Bases |
| 21 | +- `BaseEntityConfiguration<TEntity, TKey>` — auto-configures primary key |
| 22 | +- `AuditableEntityConfiguration<TEntity, TKey>` — adds audit columns and `CreatedAt` index |
| 23 | +- `SoftDeletableEntityConfiguration<TEntity, TKey>` — adds `IsDeleted` default and composite index |
| 24 | + |
| 25 | +#### Interfaces |
| 26 | +- `IAuditable` — audit timestamp contract |
| 27 | +- `IFullAuditable` — field-level change history contract |
| 28 | +- `ISoftDeletable` — soft delete contract |
| 29 | +- `IConcurrencyAware` — optimistic concurrency contract |
| 30 | +- `IUserProvider` — current user resolution contract |
| 31 | +- `IRepository<T>` / `IReadRepository<T>` — repository contracts |
| 32 | +- `IUnitOfWork` — unit of work contract |
| 33 | +- `ISpecification<T>` / `ISpecification<T, TResult>` — specification pattern contracts |
| 34 | + |
| 35 | +#### Soft Delete |
| 36 | +- `SoftDeleteInterceptor` — intercepts `DELETE` and converts to `UPDATE SET IsDeleted = true` |
| 37 | +- Global query filter automatically excludes soft-deleted rows |
| 38 | +- `IncludeDeleted()` — bypass the filter to include soft-deleted rows |
| 39 | +- `OnlyDeleted()` — query only soft-deleted rows |
| 40 | +- `GetDeletedAsync()` — load all soft-deleted rows from a `DbSet<T>` |
| 41 | +- `Restore()` — clear soft-delete flags on an entity |
| 42 | +- `HardDelete()` — permanently remove a record bypassing the interceptor |
| 43 | +- Optional cascade soft delete for loaded navigation properties |
| 44 | + |
| 45 | +#### Audit Trail |
| 46 | +- `AuditInterceptor` — auto-stamps `CreatedAt`/`CreatedBy` on insert and `UpdatedAt`/`UpdatedBy` on update |
| 47 | +- Protects `CreatedAt` and `CreatedBy` from being overwritten on update |
| 48 | +- Optional full audit log (`IFullAuditable`) — records every field change in an `AuditLog` table |
| 49 | + |
| 50 | +#### Repository and Unit of Work |
| 51 | +- `Repository<T>` — generic implementation of `IRepository<T>` and `IReadRepository<T>` |
| 52 | +- `UnitOfWork<TContext>` — groups multiple repository operations into a single `SaveChangesAsync` |
| 53 | +- All registered automatically via `AddEfCoreExtensions` |
| 54 | + |
| 55 | +#### Specification Pattern |
| 56 | +- `Specification<T>` base class with `AddCriteria`, `AddInclude`, `ApplyOrderBy`, `ApplyPaging`, `ApplyAsNoTracking`, `ApplyAsSplitQuery` |
| 57 | +- `Specification<T, TResult>` — projecting specification with `ApplySelector` |
| 58 | +- `And()` / `Or()` combinators — compose specifications at runtime |
| 59 | +- `SpecificationBuilder<T>` — fluent inline builder for one-off queries |
| 60 | +- `ApplySpecification` extension — apply any spec to an `IQueryable<T>` |
| 61 | +- `ToListAsync(ISpecification<T, TResult>)` — project and materialise in one call |
| 62 | +- `ToPagedFromSpecAsync` — apply spec criteria then paginate |
| 63 | + |
| 64 | +#### Pagination |
| 65 | +- `ToPagedAsync` — offset pagination returning `PagedResult<T>` with full metadata |
| 66 | +- `SelectToPagedAsync` — project before materialising, then paginate |
| 67 | +- `ToKeysetPagedAsync` — keyset/cursor pagination returning `KeysetPagedResult<T>` |
| 68 | +- `PagedResult<T>.Map<TDestination>` — map items to a different type while preserving metadata |
| 69 | + |
| 70 | +#### Dynamic Filters and Sorting |
| 71 | +- `ApplyFilters(FilterDescriptor[])` — apply runtime filter arrays to any `IQueryable<T>` |
| 72 | +- Supported operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `startswith`, `endswith`, `isnull`, `isnotnull`, `in`, `between` |
| 73 | +- `ApplySorts(SortDescriptor[])` — apply runtime multi-column sorting |
| 74 | +- Dot-separated nested property paths supported in both filters and sorts |
| 75 | + |
| 76 | +#### Query Helpers |
| 77 | +- `GetByIdAsync` / `GetByIdOrThrowAsync` — single entity lookup by primary key |
| 78 | +- `GetByIdsAsync` — batch lookup translating to `WHERE key IN (...)` |
| 79 | +- `GetAllAsync` — load all rows from a `DbSet<T>` |
| 80 | +- `FindAsync` — load by predicate or specification |
| 81 | +- `ExistsAsync` — existence check by key or predicate |
| 82 | +- `WhereIf` / `WhereIfNotNull` / `WhereIfNotEmpty` — conditional filtering |
| 83 | +- `OrderByDynamic` / `ThenByDynamic` — sort by property name string |
| 84 | +- `SelectToListAsync` / `SelectFirstOrDefaultAsync` / `SelectDistinctAsync` — projection helpers |
| 85 | +- `WithNoTracking` — alias for `AsNoTracking` |
| 86 | +- `RemoveRangeAsync` — load and stage for deletion by predicate |
| 87 | + |
| 88 | +#### DbContext Utilities |
| 89 | +- `ExecuteInTransactionAsync` — wraps work in a transaction respecting EF Core execution strategy |
| 90 | +- `DetachAll` — clears all tracked entities from the change tracker |
| 91 | +- `TruncateAsync<T>` — truncates a table by entity type using EF Core metadata |
| 92 | + |
| 93 | +#### Slow Query Logging |
| 94 | +- `SlowQueryInterceptor` — logs a warning for any query exceeding a configurable threshold |
| 95 | +- Configured via `LogSlowQueries(TimeSpan threshold)` |
| 96 | + |
| 97 | +#### Structured Exceptions |
| 98 | +- `EfCoreException` — abstract base for all EfCoreKit exceptions |
| 99 | +- `EntityNotFoundException` — thrown by `GetByIdOrThrowAsync` and `RemoveByIdAsync` |
| 100 | +- `ConcurrencyConflictException` — thrown automatically on stale row-version conflict |
| 101 | +- `DuplicateEntityException` — throw manually when catching unique constraint violations |
| 102 | +- `InvalidFilterException` — thrown by `ApplyFilters` for invalid filter descriptors |
| 103 | + |
| 104 | +#### DI Registration |
| 105 | +- `AddEfCoreExtensions<TContext>` — single call registers DbContext, interceptors, repository, and unit of work |
| 106 | +- `EfCoreDbContext<TContext>` — optional base context that wires interceptors and query filters automatically |
0 commit comments