The Repository pattern provides a clean abstraction over data access, allowing you to switch between different persistence technologies without changing your application logic. The framework includes implementations for both SQL Server (Entity Framework Core) and MongoDB.
Core Interface
IRepository<TEntity>
The main repository interface that defines standard CRUD operations.
Synchronously adds an entity to the repository. Parameters:
entity: The entity to add
Returns: The ID of the created entity
Asynchronously adds an entity to the repository. Parameters:
entity: The entity to add
Returns: The ID of the created entity
Finds a single entity by its key values. Parameters:
keyValues: The primary key value(s)
Returns: The entity or null if not found
Asynchronously finds a single entity by its key values. Parameters:
keyValues: The primary key value(s)
Returns: The entity or null if not found
Retrieves all entities from the repository. Returns: List of all entities
Asynchronously retrieves all entities from the repository. Returns: List of all entities
Updates an existing entity. Parameters:
id: The entity ID
entity: The updated entity
Removes an entity by its key values. Parameters:
keyValues: The primary key value(s)
Counts entities matching a filter expression. Parameters:
filter: Lambda expression for filtering
Returns: Count of matching entities
Asynchronously counts entities matching a filter expression. Parameters:
filter: Lambda expression for filtering
Returns: Count of matching entities
Core.Application.Repositories/IRepository.cs
using System . Linq . Expressions ;
namespace Core . Application . Repositories
{
public interface IRepository < TEntity >
{
object Add ( TEntity entity );
Task < object > AddAsync ( TEntity entity );
long Count ( Expression < Func < TEntity , bool >> filter );
Task < long > CountAsync ( Expression < Func < TEntity , bool >> filter );
List < TEntity > FindAll ();
Task < List < TEntity >> FindAllAsync ();
TEntity FindOne ( params object [] keyValues );
Task < TEntity > FindOneAsync ( params object [] keyValues );
void Remove ( params object [] keyValues );
void Update ( object id , TEntity entity );
}
}
SQL Server Implementation
The SQL Server implementation uses Entity Framework Core for data access.
BaseRepository<TEntity> (SQL)
Core.Infraestructure.Repositories.Sql/BaseRepository.cs
using Core . Application . Repositories ;
using Microsoft . EntityFrameworkCore ;
using System . Linq . Expressions ;
namespace Core . Infraestructure . Repositories . Sql
{
public class BaseRepository < TEntity > : IRepository < TEntity > where TEntity : class
{
public DbContext Context { get ; private set ; }
public DbSet < TEntity > Repository { get ; private set ; }
public BaseRepository ( DbContext context )
{
Context = context ;
Repository = Context . Set < TEntity >();
}
public async Task < object > AddAsync ( TEntity entity )
{
await Repository . AddAsync ( entity );
await Context . SaveChangesAsync ();
return ( object ) Context . Entry ( entity ). Property ( "Id" ). CurrentValue ;
}
public async Task < List < TEntity >> FindAllAsync ()
{
return await Repository . ToListAsync ();
}
public async Task < TEntity > FindOneAsync ( params object [] keyValues )
{
return await Repository . FindAsync ( keyValues );
}
public async Task < long > CountAsync ( Expression < Func < TEntity , bool >> filter )
{
return Convert . ToInt64 ( await Repository . CountAsync ( filter ));
}
public void Update ( object id , TEntity entity )
{
TEntity foundEntity = FindOne ( id );
if ( foundEntity != null )
{
Repository . Update ( entity );
Context . SaveChanges ();
}
}
public void Remove ( params object [] keyValues )
{
TEntity entity = FindOne ( keyValues );
if ( entity != null )
{
Repository . Remove ( entity );
Context . SaveChanges ();
}
}
}
}
Features
Change Tracking Automatic tracking of entity changes through EF Core.
LINQ Support Full LINQ query support for complex queries.
Migrations Database schema management through EF Core migrations.
Transactions Built-in transaction support via DbContext.
MongoDB Implementation
The MongoDB implementation provides a document-based data access layer.
BaseRepository<TEntity> (MongoDB)
Core.Infraestructure.Repositories.MongoDb/BaseRepository.cs
using Core . Application . Repositories ;
using MongoDB . Driver ;
using System . Linq . Expressions ;
namespace Core . Infraestructure . Repositories . MongoDb
{
public class BaseRepository < TEntity > : IRepository < TEntity > where TEntity : class
{
public DbContext Context { get ; private set ; }
public IMongoCollection < TEntity > Collection { get ; private set ; }
public BaseRepository ( DbContext context )
{
Context = context ;
Collection = Context . GetCollection < TEntity >();
}
public async Task < object > AddAsync ( TEntity entity )
{
await Collection . InsertOneAsync ( entity );
var property = typeof ( TEntity ). GetProperty ( "Id" ) ?? typeof ( TEntity ). GetProperty ( "_id" );
return ( object ) property ? . GetValue ( entity );
}
public async Task < List < TEntity >> FindAllAsync ()
{
FilterDefinition < TEntity > filter = new BsonDocumentFilterDefinition < TEntity >([]);
return await Collection . Find ( filter ). ToListAsync ();
}
public async Task < TEntity > FindOneAsync ( params object [] keyValues )
{
FilterDefinition < TEntity > filter = Builders < TEntity >. Filter . Eq ( "_id" , keyValues [ 0 ]);
return await Collection . Find ( filter ). SingleOrDefaultAsync ();
}
public async Task < long > CountAsync ( Expression < Func < TEntity , bool >> filter )
{
return await Collection . CountDocumentsAsync ( filter );
}
public void Update ( object id , TEntity entity )
{
FilterDefinition < TEntity > filter = Builders < TEntity >. Filter . Eq ( "_id" , id );
Collection . ReplaceOne ( filter , entity );
}
public void Remove ( params object [] keyValues )
{
FilterDefinition < TEntity > filter = Builders < TEntity >. Filter . Eq ( "_id" , keyValues [ 0 ]);
Collection . DeleteOne ( filter );
}
}
}
Features
Schema-less Flexible document structure without rigid schemas.
High Performance Optimized for high-throughput operations.
Scalability Horizontal scaling through sharding.
Native BSON Efficient binary JSON storage format.
Creating a Domain Repository
Extend the base repository for domain-specific operations:
Application/Repositories/IDummyEntityRepository.cs
using Core . Application . Repositories ;
using Domain . Entities ;
namespace Application . Repositories
{
public interface IDummyEntityRepository : IRepository < DummyEntity >
{
// Add domain-specific query methods
Task < List < DummyEntity >> FindByPropertyAsync ( string propertyValue );
Task < bool > ExistsByIdAsync ( string id );
}
}
Implementation (SQL Server)
Infrastructure/Repositories/Sql/DummyEntityRepository.cs
using Application . Repositories ;
using Core . Infraestructure . Repositories . Sql ;
using Domain . Entities ;
using Microsoft . EntityFrameworkCore ;
namespace Infrastructure . Repositories . Sql
{
public class DummyEntityRepository : BaseRepository < DummyEntity >, IDummyEntityRepository
{
public DummyEntityRepository ( DbContext context ) : base ( context )
{
}
public async Task < List < DummyEntity >> FindByPropertyAsync ( string propertyValue )
{
return await Repository
. Where ( e => e . DummyPropertyOne == propertyValue )
. ToListAsync ();
}
public async Task < bool > ExistsByIdAsync ( string id )
{
return await Repository . AnyAsync ( e => e . Id == id );
}
}
}
Implementation (MongoDB)
Infrastructure/Repositories/MongoDb/DummyEntityRepository.cs
using Application . Repositories ;
using Core . Infraestructure . Repositories . MongoDb ;
using Domain . Entities ;
using MongoDB . Driver ;
namespace Infrastructure . Repositories . MongoDb
{
public class DummyEntityRepository : BaseRepository < DummyEntity >, IDummyEntityRepository
{
public DummyEntityRepository ( DbContext context ) : base ( context )
{
}
public async Task < List < DummyEntity >> FindByPropertyAsync ( string propertyValue )
{
var filter = Builders < DummyEntity >. Filter . Eq ( e => e . DummyPropertyOne , propertyValue );
return await Collection . Find ( filter ). ToListAsync ();
}
public async Task < bool > ExistsByIdAsync ( string id )
{
var filter = Builders < DummyEntity >. Filter . Eq ( "_id" , id );
var count = await Collection . CountDocumentsAsync ( filter );
return count > 0 ;
}
}
}
Registration
Register repositories based on configuration:
Infrastructure/Registrations/InfraestructureServicesRegistration.cs
public static IServiceCollection AddRepositories (
this IServiceCollection services ,
IConfiguration configuration )
{
string dbType = configuration [ "Configurations:UseDatabase" ];
services . CreateDataBase ( dbType , configuration );
return services ;
}
SQL Server Configuration
{
"Configurations" : {
"UseDatabase" : "SqlServer"
},
"ConnectionStrings" : {
"SqlServer" : "Server=localhost;Database=HybridDDD;Trusted_Connection=True;"
}
}
MongoDB Configuration
{
"Configurations" : {
"UseDatabase" : "MongoDb"
},
"ConnectionStrings" : {
"MongoDb" : "mongodb://localhost:27017/HybridDDD"
}
}
Usage in Application Services
Application/UseCases/DummyEntity/Commands/CreateDummyEntityHandler.cs
public class CreateDummyEntityHandler : IRequestCommandHandler < CreateDummyEntityCommand , string >
{
private readonly IDummyEntityRepository _repository ;
public CreateDummyEntityHandler ( IDummyEntityRepository repository )
{
_repository = repository ?? throw new ArgumentNullException ( nameof ( repository ));
}
public async Task < string > Handle ( CreateDummyEntityCommand request , CancellationToken cancellationToken )
{
var entity = new DummyEntity ( request . dummyPropertyOne , request . dummyPropertyTwo );
// Check if entity exists
bool exists = await _repository . ExistsByIdAsync ( entity . Id );
if ( exists ) throw new EntityDoesExistException ();
// Add entity
object createdId = await _repository . AddAsync ( entity );
return createdId . ToString ();
}
}
Best Practices
Always inject repository interfaces (IDummyEntityRepository), never concrete implementations. This enables testing and database switching.
Avoid exposing IQueryable from repositories. Return materialized collections to prevent query logic from leaking into the application layer.
For complex queries, create dedicated repository methods rather than using generic FindAll() and filtering in memory.
Advanced Patterns
Unit of Work
For SQL Server, the DbContext acts as a Unit of Work:
public async Task < string > Handle ( CreateMultipleEntitiesCommand request , CancellationToken cancellationToken )
{
using var transaction = await _context . Database . BeginTransactionAsync ();
try
{
await _repository1 . AddAsync ( entity1 );
await _repository2 . AddAsync ( entity2 );
await transaction . CommitAsync ();
return "Success" ;
}
catch
{
await transaction . RollbackAsync ();
throw ;
}
}
Specification Pattern
Implement reusable query specifications:
public interface ISpecification < T >
{
Expression < Func < T , bool >> Criteria { get ; }
List < Expression < Func < T , object >>> Includes { get ; }
}
public static class SpecificationExtensions
{
public static IQueryable < T > ApplySpecification < T >(
this IQueryable < T > query ,
ISpecification < T > spec ) where T : class
{
var queryableResultWithIncludes = spec . Includes
. Aggregate ( query , ( current , include ) => current . Include ( include ));
return queryableResultWithIncludes . Where ( spec . Criteria );
}
}