Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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:
- Writes the current build number into
Directory.Build.propsas<Version>%build.number%</Version>. - Builds and packs both projects.
- Pushes the resulting
.nupkgfiles 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
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).
// 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>>();