feat: rebuild base repository hierarchy, add readme and agents
- 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>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user