EF Core & Repository Pattern
Relevant source files
The following files were used as context for generating this wiki page:
- BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreRepository.cs
- BuildingBlocks/BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs
- Core/Scheduling/Scheduling.Infrastructure/Persistence/Configurations/PatientConfiguration.cs
- BuildingBlocks/BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs
- claude.md
- Core/Scheduling/Scheduling.Infrastructure/Persistence/Migrations/SchedulingDbContextModelSnapshot.cs
The project utilizes Entity Framework Core (EF Core) as the primary Object-Relational Mapper (ORM) to implement persistence for Domain-Driven Design (DDD) tactical patterns. The implementation centralizes around a generic Repository pattern and a robust Unit of Work (UoW) system that manages database transactions, domain event dispatching, and integration event queuing for outbox persistence.
Sources: claude.md:20-29, BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:11-35
Core Architecture and Components
The persistence layer is structured to enforce DDD rules, such as ensuring aggregates are accessed only through roots and encapsulating business logic within the domain. The BuildingBlocks.Infrastructure.EfCore namespace provides base implementations for repositories and unit of work that are shared across Bounded Contexts like Scheduling and Billing.
Sources: claude.md:143-149, BuildingBlocks.Infrastructure.EfCore/EfCoreRepository.cs:7-14
Generic Repository Implementation
The EfCoreRepository<TContext, TEntity> provides a standard set of data access methods while maintaining abstraction from the underlying DbSet. It supports complex querying through IQueryable expressions and specialized DTO projections using the IEntityDto interface.
The diagram shows the relationship between the generic Repository interface and its EF Core-specific implementation.
Sources: BuildingBlocks.Infrastructure.EfCore/EfCoreRepository.cs:7-35
Unit of Work and Transaction Management
The EfCoreUnitOfWork<TContext> manages the lifecycle of database transactions. It features a depth counter (_transactionDepth) to support nested transaction calls, ensuring that a commit only occurs at the outermost level.
| Component | Responsibility |
|---|---|
BeginTransactionAsync | Increments depth and starts a new IDbContextTransaction if none is active. |
SaveChangesAsync | Dispatches domain events and publishes integration events to the outbox before calling EF's SaveChangesAsync. |
CloseTransactionAsync | Decrements depth and commits or rolls back the transaction when depth reaches zero. |
DispatchDomainEventsAsync | Scans the ChangeTracker for entities with events and publishes them via IMediator. |
Sources: BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:11-40, BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:112-154
Data Flow and Persistence Logic
The persistence flow ensures that all side effects (domain events and integration events) are handled within the same transaction scope as the primary data changes.
This flowchart illustrates the sequence of operations within the Unit of Work during a typical data modification.
Sources: BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:42-55, BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:134-154
Integration with Messaging (Outbox Pattern)
The Unit of Work integrates with the messaging system (MassTransit or Wolverine) to implement the Outbox pattern. Integration events are queued and written to specific database tables (e.g., Scheduling_OutboxMessage) during the same transaction that persists domain data.
Sources: BuildingBlocks.Infrastructure.EfCore/EfCoreUnitOfWork.cs:88-110, Scheduling.Infrastructure/Persistence/Migrations/SchedulingDbContextModelSnapshot.cs:100-115
Entity Configuration and Mapping
Domain objects are mapped to the SQL Server schema using IEntityTypeConfiguration<T>. This allows for clean separation between domain logic and persistence concerns, including the handling of Value Objects and Smart Enums.
Patient Configuration Example
The PatientConfiguration class defines how the Patient aggregate is mapped to the Patients table. It specifically handles:
- Key Mapping: Defining the
Idas the primary key. - Encapsulation: Using
builder.Ignore(p => p.DomainEvents)to prevent domain event collections from being persisted. - Conversions: Mapping the
PatientStatusSmart Enum to a string in the database.
Sources: Scheduling.Infrastructure/Persistence/Configurations/PatientConfiguration.cs:7-25
Table Schema Summary (Scheduling Context)
| Table | Type | Purpose |
|---|---|---|
Patients | Aggregate | Stores patient identity and status data. |
Scheduling_InboxState | Infrastructure | Tracks consumed messages for idempotency. |
Scheduling_OutboxMessage | Infrastructure | Stores integration events pending delivery. |
Scheduling_OutboxState | Infrastructure | Manages delivery state for outbox messages. |
Sources: Scheduling.Infrastructure/Persistence/Migrations/SchedulingDbContextModelSnapshot.cs:15-180
Testing and Transaction Isolation
The project employs a specialized testing strategy using TestBase<TContext> and SQLite in-memory databases to ensure isolation.
- Transaction Rollback: Every test starts a transaction in
TestInitializeand forces a rollback inTestCleanupusingUow.CloseTransactionAsync(new Exception("Test rollback")). - Nested Transaction Verification: Extensive unit tests verify that
EfCoreUnitOfWorkcorrectly handles deeply nested transactions without premature commits.
Sources: BuildingBlocks.Tests/TestBase.cs:34-75, BuildingBlocks.Tests/EfCoreUnitOfWorkTests.cs:140-167
Summary
The EF Core and Repository Pattern implementation in this project provides a consistent, transactional foundation for all Bounded Contexts. By centralizing logic in the EfCoreUnitOfWork, the system guarantees that domain events are processed, integration events are persisted in the outbox, and data changes are committed atomically. This architecture strictly adheres to DDD tactical patterns while leveraging EF Core's advanced features like ChangeTracker interceptors and value conversions.