Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@

MIT License

Copyright (c)2026 Isaiah Clifford Opoku

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ Every .NET project with EF Core ends up writing the same plumbing: soft delete f
| **Entity Configuration Bases** | Fluent config base classes that auto-wire keys, indexes, and soft-delete defaults |
| **Soft Delete** | Mark records as deleted with automatic global query filters; restore or hard-delete on demand |
| **Audit Trail** | Auto-stamp `CreatedAt/By`, `UpdatedAt/By`; optional field-level `AuditLog` history |
| **Multi-Tenancy** | Automatic tenant filtering so each tenant only sees their own data |
| **Repository + Unit of Work** | Generic `IRepository<T>` / `IReadRepository<T>` backed by `IUnitOfWork` |
| **Specification Pattern** | Composable query specs with `And()` / `Or()` combinators, projection, and multi-column ordering |
| **Pagination** | Offset (`ToPagedAsync`) and keyset/cursor (`ToKeysetPagedAsync`) pagination with `PagedResult<T>` |
Expand Down Expand Up @@ -250,7 +249,6 @@ await context.TruncateAsync<AuditLog>();
| [Base Entities](docs/base-entities.md) | Entity hierarchy, configuration base classes |
| [Soft Delete](docs/soft-delete.md) | ISoftDeletable, lifecycle methods, restoring records |
| [Audit Trail](docs/audit-trail.md) | IAuditable, IFullAuditable, field-level AuditLog |
| [Multi-Tenancy](docs/multi-tenancy.md) | ITenantEntity, automatic filtering, tenant validation |
| [Repository & Unit of Work](docs/repository-uow.md) | IRepository, IReadRepository, IUnitOfWork |
| [Specification Pattern](docs/specifications.md) | Spec classes, And/Or combinators, projection specs |
| [Pagination](docs/pagination.md) | Offset and keyset pagination, PagedResult |
Expand Down
4 changes: 4 additions & 0 deletions docs/audit-trail.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,7 @@ The audit interceptor marks `CreatedAt` and `CreatedBy` as `IsModified = false`
## Combining with Soft Delete

When an entity implements both `IAuditable` and `ISoftDeletable`, a soft delete triggers a `Modified` state change — so `UpdatedAt` and `UpdatedBy` are stamped at the moment of deletion.

---

[← Soft Delete](soft-delete.md) | [Repository & Unit of Work →](repository-uow.md)
4 changes: 4 additions & 0 deletions docs/base-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,7 @@ catch (ConcurrencyConflictException ex)
// ex.EntityId — primary key of the conflicting row
}
```

---

[← Getting Started](getting-started.md) | [Soft Delete →](soft-delete.md)
4 changes: 4 additions & 0 deletions docs/dbcontext-utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ await context.TruncateAsync<AuditLog>();
| `ExecuteInTransactionAsync<T>(Func<Task<T>>)` | Same, returning a value |
| `DetachAll()` | Clear the change tracker |
| `TruncateAsync<T>()` | Truncate a table by entity type |

---

[← Query Helpers](query-helpers.md) | [Exceptions →](exceptions.md)
4 changes: 4 additions & 0 deletions docs/dynamic-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,7 @@ var page = await context.Customers
.ApplySorts(sorts)
.ToPagedAsync(page: 1, pageSize: 20);
```

---

[← Pagination](pagination.md) | [Query Helpers →](query-helpers.md)
4 changes: 4 additions & 0 deletions docs/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,7 @@ catch (InvalidFilterException ex)
return BadRequest(ex.Message);
}
```

---

**Previous:** [← DbContext Utilities](dbcontext-utilities.md)
4 changes: 4 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ Once configured, EfCoreKit handles the following via EF Core interceptors:
- [Query Helpers](query-helpers.md) — WhereIf, OrderByDynamic, DbSet extensions
- [DbContext Utilities](dbcontext-utilities.md) — Transactions, DetachAll, TruncateAsync
- [Exceptions](exceptions.md) — All exception types, when they're thrown, what to catch

---

**Next:** [Base Entities →](base-entities.md)
4 changes: 4 additions & 0 deletions docs/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,7 @@ public sealed class KeysetPagedResult<T>
| **Infinite scroll** | Possible | Ideal |
| **Stable with concurrent inserts** | Can skip / duplicate rows | Always consistent |
| **Requires ordered query** | Recommended | Required |

---

[← Specification Pattern](specifications.md) | [Dynamic Filters →](dynamic-filters.md)
4 changes: 4 additions & 0 deletions docs/query-helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,7 @@ var spec = new SpecificationBuilder<Product>()

var products = await context.Products.ApplySpecification(spec).ToListAsync();
```

---

[← Dynamic Filters](dynamic-filters.md) | [DbContext Utilities →](dbcontext-utilities.md)
4 changes: 4 additions & 0 deletions docs/repository-uow.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,7 @@ var orders = await repo.FindAsync(spec, ct);
```

See [Specification Pattern](specifications.md) for details.

---

[← Audit Trail](audit-trail.md) | [Specification Pattern →](specifications.md)
4 changes: 4 additions & 0 deletions docs/soft-delete.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,7 @@ When an entity implements both `IAuditable` and `ISoftDeletable` (as `SoftDeleta
## Combining with Multi-Tenancy

Soft-deleted rows from other tenants remain invisible. The tenant filter and soft-delete filter are both applied independently.

---

[← Base Entities](base-entities.md) | [Audit Trail →](audit-trail.md)
4 changes: 4 additions & 0 deletions docs/specifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ var page = await context.Orders.ToPagedFromSpecAsync(
```

`ToPagedFromSpecAsync` ignores any `Skip`/`Take` set on the spec — pagination is controlled by the `page`/`pageSize` parameters.

---

[← Repository & Unit of Work](repository-uow.md) | [Pagination →](pagination.md)
Loading