15c58522ef
- replace EfRepository/BaseDbContext/UtcValueConverter with BaseEfRepository and BaseRepository - add IEfRepository interface hierarchy - consolidate IEntity into Entity.cs, remove standalone IEntity.cs - add PagedResult - adjust all namespaces to HrynCo.DAL.Abstract / HrynCo.DAL.EF - add README.md with solution overview, versioning rules, class diagram - add AGENTS.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
208 lines
6.9 KiB
Markdown
208 lines
6.9 KiB
Markdown
# hrynco-ef
|
|
|
|
Reusable Entity Framework Core base library for HrynCo applications.
|
|
|
|
## Solution
|
|
|
|
The solution (`hrynco-ef.slnx`) contains two projects:
|
|
|
|
| Project | Description |
|
|
|---|---|
|
|
| `HrynCo.DAL.Abstract` | Infrastructure-agnostic contracts: entities, repository interfaces, unit of work, transactions, pagination. No EF Core dependency. |
|
|
| `HrynCo.DAL.EF` | Entity Framework Core implementations of the abstract contracts. Depends on `HrynCo.DAL.Abstract` and EF Core. |
|
|
|
|
The split allows consuming projects to reference only `HrynCo.DAL.Abstract` in domain/application layers, keeping those layers free of EF Core.
|
|
|
|
## Versioning
|
|
|
|
Versions are managed entirely on the TeamCity side — **do not set `<Version>` in `.csproj` files**.
|
|
|
|
At publish time, the TC `HrynCo / HrynCo.EF / publish` build:
|
|
|
|
1. Writes the current build number into `Directory.Build.props` as `<Version>%build.number%</Version>`.
|
|
2. Builds and packs both projects.
|
|
3. Pushes the resulting `.nupkg` files to nuget.org.
|
|
|
|
The build number follows the pattern `1.0.<counter>` (e.g. `1.0.6`, `1.0.7`, …). The counter increments automatically on each successful publish run. To release a new version, merge to `main` — the publish build triggers automatically.
|
|
|
|
To bump the major or minor version, update the build number pattern in TC: **HrynCo → HrynCo.EF → publish → Edit Configuration → General → Build number format**.
|
|
|
|
## Class diagram
|
|
|
|
```mermaid
|
|
classDiagram
|
|
namespace HrynCo_DAL_Abstract {
|
|
class IEntity {
|
|
<<interface>>
|
|
+object Id
|
|
+DateTimeOffset Created
|
|
+DateTimeOffset? Updated
|
|
}
|
|
class IEntityTId {
|
|
<<interface>>
|
|
+TId Id
|
|
}
|
|
class EntityTId {
|
|
<<abstract>>
|
|
+TId Id
|
|
+DateTimeOffset Created
|
|
+DateTimeOffset? Updated
|
|
}
|
|
class Entity {
|
|
<<abstract>>
|
|
+Guid Id
|
|
}
|
|
class NamedEntity {
|
|
<<abstract>>
|
|
+string Name
|
|
}
|
|
class IUnitOfWork {
|
|
<<interface>>
|
|
+BeginTransactionAsync() Task~ITransaction~
|
|
+GetCurrentTransaction() ITransaction?
|
|
+ExecuteInTransactionAsync(action) Task
|
|
+ExecuteInTransactionAsync~TResult~(action) Task~TResult~
|
|
}
|
|
class ITransaction {
|
|
<<interface>>
|
|
+CommitAsync() Task
|
|
+RollbackAsync() Task
|
|
+DisposeAsync() ValueTask
|
|
}
|
|
class PagedResultT {
|
|
<<sealed>>
|
|
+IReadOnlyList~T~ Items
|
|
+int Page
|
|
+int PageSize
|
|
+int TotalCount
|
|
}
|
|
}
|
|
|
|
namespace HrynCo_DAL_EF {
|
|
class IEfRepository {
|
|
<<interface>>
|
|
+ClearChangeTracker()
|
|
}
|
|
class IEfRepositoryTEntityTId {
|
|
<<interface>>
|
|
+Add(entity) TEntity
|
|
+AddAsync(entity) Task~TEntity~
|
|
+Delete(id)
|
|
+DeleteAsync(id) Task
|
|
+Get(filter, orderBy, includes) IQueryable~TEntity~
|
|
+GetAll() IQueryable~TEntity~
|
|
+GetAllAsync() Task~List~TEntity~~
|
|
+GetById(id) TEntity?
|
|
+GetByIdAsync(id) Task~TEntity?~
|
|
+Exists(id) Task~bool~
|
|
+UpdateAsync(entity) Task
|
|
+SaveChangesAsync() Task
|
|
}
|
|
class IEfRepositoryTEntity {
|
|
<<interface>>
|
|
}
|
|
class BaseEfRepositoryTDbContextTEntityTEntityId {
|
|
<<abstract>>
|
|
+TDbContext DbContext
|
|
+Add() TEntity
|
|
+AddAsync() Task~TEntity~
|
|
+Delete()
|
|
+DeleteAsync() Task
|
|
+Get() IQueryable~TEntity~
|
|
+GetAll() IQueryable~TEntity~
|
|
+GetAllAsync() Task~List~TEntity~~
|
|
+GetById() TEntity?
|
|
+GetByIdAsync() Task~TEntity?~
|
|
+Exists() Task~bool~
|
|
+Update()
|
|
+UpdateAsync() Task
|
|
+SaveChangesAsync() Task
|
|
#DoRemove()
|
|
}
|
|
class BaseRepositoryTEfRepositoryTDbContextTEntityTEntityId {
|
|
<<abstract>>
|
|
#EfRepository TEfRepository
|
|
#CreateEfRepository()* TEfRepository
|
|
}
|
|
class EfUnitOfWorkTDbContext {
|
|
+BeginTransactionAsync() Task~ITransaction~
|
|
+GetCurrentTransaction() ITransaction?
|
|
+ExecuteInTransactionAsync() Task
|
|
}
|
|
class EfTransactionAdapter {
|
|
+CommitAsync() Task
|
|
+RollbackAsync() Task
|
|
+DisposeAsync() ValueTask
|
|
}
|
|
}
|
|
|
|
IEntityTId --|> IEntity : extends
|
|
EntityTId ..|> IEntityTId : implements
|
|
Entity --|> EntityTId : extends (TId=Guid)
|
|
NamedEntity --|> Entity : extends
|
|
|
|
IEfRepositoryTEntityTId --|> IEfRepository : extends
|
|
IEfRepositoryTEntity --|> IEfRepositoryTEntityTId : extends (TId=int)
|
|
BaseEfRepositoryTDbContextTEntityTEntityId ..|> IEfRepositoryTEntityTId : implements
|
|
BaseRepositoryTEfRepositoryTDbContextTEntityTEntityId --> BaseEfRepositoryTDbContextTEntityTEntityId : uses (lazy)
|
|
|
|
EfUnitOfWorkTDbContext ..|> IUnitOfWork : implements
|
|
EfTransactionAdapter ..|> ITransaction : implements
|
|
EfUnitOfWorkTDbContext --> EfTransactionAdapter : creates
|
|
```
|
|
|
|
## Packages
|
|
|
|
### `HrynCo.DAL.Abstract`
|
|
|
|
Abstract DAL contracts — entities, repository interfaces, unit of work, transactions, and pagination.
|
|
|
|
| Type | Description |
|
|
|---|---|
|
|
| `IEntity` / `IEntity<TId>` | Base entity contracts |
|
|
| `Entity<TId>` / `Entity` | Base entity implementations with auto-generated `Id` |
|
|
| `NamedEntity` | Entity with a `Name` property |
|
|
| `IRepository<T>` | Generic async repository interface |
|
|
| `IUnitOfWork` | Unit of work interface with transaction support |
|
|
| `ITransaction` | Async transaction contract |
|
|
| `PagedResult<T>` | Pagination result wrapper |
|
|
|
|
### `HrynCo.DAL.EF`
|
|
|
|
Entity Framework Core implementations of the abstract contracts.
|
|
|
|
| Type | Description |
|
|
|---|---|
|
|
| `BaseRepository<T>` | Base repository with common CRUD operations |
|
|
| `BaseEfRepository<T>` | EF Core repository with `DbContext` access |
|
|
| `IEfRepository<T>` | EF-specific repository interface |
|
|
| `EfUnitOfWork` | EF Core unit of work implementation |
|
|
| `EfTransactionAdapter` | Adapts EF transactions to `ITransaction` |
|
|
|
|
## Usage
|
|
|
|
Reference `HrynCo.DAL.Abstract` for contracts only (e.g. in domain/application layers).
|
|
Reference `HrynCo.DAL.EF` for the full EF Core implementation (infrastructure layer).
|
|
|
|
```csharp
|
|
// 1. Define your entity
|
|
public class Product : Entity
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
}
|
|
|
|
// 2. Implement your repository
|
|
public class ProductRepository : BaseEfRepository<Product>
|
|
{
|
|
public ProductRepository(YourDbContext context) : base(context) { }
|
|
}
|
|
|
|
// 3. Register in DI
|
|
services.AddScoped<IRepository<Product>, ProductRepository>();
|
|
services.AddScoped<IUnitOfWork, EfUnitOfWork<YourDbContext>>();
|
|
```
|
|
|
|
## Related
|
|
|
|
- [IT-642](https://yt.grynco.com.ua/issue/IT-642) — Extract `PagedResult` builder as reusable `IQueryable<T>` extension
|