A powerful and flexible repository pattern implementation for Entity Framework Core that simplifies data access and provides a clean abstraction layer for your .NET applications.
- Generic Repository Pattern: Type-safe repository implementations for any entity
- Query Repository: Optimized read-only operations with advanced querying capabilities
- Unit of Work Pattern: Transaction management with scoped operations
- Pagination Support: Built-in pagination with
PagedList<T>andPagingDetails - Sorting Support: Flexible sorting with
SortingDetails<T>andSortItem - Dependency Injection: Seamless integration with .NET DI container
- Save Change Strategies: Configurable save strategies (PerOperation/PerUnitOfWork)
- Async/Await Support: Full async support throughout the library
- Entity Framework Core Integration: Built specifically for EF Core 6.0+
dotnet add package BerrishDev.Common.RepositoryThe library follows clean architecture principles and provides:
- Repository Interfaces:
IRepository<T>andIQueryRepository<T> - EF Core Implementation:
EFCoreRepository<TDbContext, TEntity>andEfCoreQueryRepository<TDbContext, TEntity> - Unit of Work:
IUnitOfWorkandIUnitOfWorkScopefor transaction management - Pagination:
PagedList<T>andPagingDetailsfor efficient data paging - Sorting:
SortingDetails<T>,SortItem, andSortDirectionfor flexible data ordering
using Common.Repository.EfCore.Extensions;
using Common.Repository.EfCore.Options;
// In your Program.cs or Startup.cs
services.AddEfCoreDbContext<YourDbContext>(options =>
{
options.UseSqlServer(connectionString);
}, repositoryOptions: options =>
{
options.SaveChangeStrategy = SaveChangeStrategy.PerUnitOfWork;
});
services.AddUnitOfWork();public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
}public class ProductService
{
private readonly IRepository<Product> _repository;
private readonly IQueryRepository<Product> _queryRepository;
private readonly IUnitOfWork _unitOfWork;
public ProductService(
IRepository<Product> repository,
IQueryRepository<Product> queryRepository,
IUnitOfWork unitOfWork)
{
_repository = repository;
_queryRepository = queryRepository;
_unitOfWork = unitOfWork;
}
public async Task<Product> CreateProductAsync(Product product, CancellationToken cancellationToken = default)
{
using var scope = await _unitOfWork.CreateScopeAsync(cancellationToken);
var createdProduct = await _repository.InsertAsync(product, cancellationToken);
await scope.CompletAsync(cancellationToken);
return createdProduct;
}
public async Task<PagedList<Product>> GetProductsAsync(
int pageIndex,
int pageSize,
CancellationToken cancellationToken = default)
{
return await _queryRepository.GetListByPageAsync(
pageIndex,
pageSize,
cancellationToken: cancellationToken);
}
}Provides full CRUD operations:
public interface IRepository<TEntity> : IQueryRepository<TEntity>
{
// Insert operations
Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default);
// Update operations
Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
// Delete operations
Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
// Query operations with update tracking
Task<List<TEntity>> GetListForUpdateAsync(
List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>? relatedProperties = null,
Expression<Func<TEntity, bool>>? predicate = null,
SortingDetails<TEntity>? sortingDetails = null,
int? skip = null,
int? take = null,
CancellationToken cancellationToken = default);
Task<TEntity> GetForUpdateAsync(
Expression<Func<TEntity, bool>> predicate,
List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>? relatedProperties = null,
CancellationToken cancellationToken = default);
}Provides read-only operations:
public interface IQueryRepository<TEntity>
{
// Basic query operations
Task<List<TEntity>> GetListAsync(
List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>? relatedProperties = null,
Expression<Func<TEntity, bool>>? predicate = null,
SortingDetails<TEntity>? sortingDetails = null,
int? skip = null,
int? take = null,
CancellationToken cancellationToken = default);
// Pagination
Task<PagedList<TEntity>> GetListByPageAsync(
int pageIndex,
int pageSize,
Expression<Func<TEntity, bool>>? predicate = null,
List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>? relatedProperties = null,
SortingDetails<TEntity>? sortingDetails = null,
CancellationToken cancellationToken = default);
// Single entity operations
Task<TEntity> GetAsync(
Expression<Func<TEntity, bool>> predicate,
List<Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>>? relatedProperties = null,
CancellationToken cancellationToken = default);
// Aggregation operations
Task<long> CountAsync(
Expression<Func<TEntity, bool>>? predicate = null,
CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(
Expression<Func<TEntity, bool>>? predicate = null,
CancellationToken cancellationToken = default);
}public interface IUnitOfWork
{
Task<IUnitOfWorkScope> CreateScopeAsync(CancellationToken cancellationToken = default);
}public interface IUnitOfWorkScope : IDisposable
{
Task CompletAsync(CancellationToken cancellationToken = default);
}public class PagedList<TItem>
{
public PagingDetails PagingDetails { get; }
public List<TItem> List { get; }
}public class PagingDetails
{
public int PageIndex { get; }
public int PageSize { get; }
public int TotalCount { get; }
public int TotalPages { get; }
public bool HasPreviousPage { get; }
public bool HasNextPage { get; }
}public class SortingDetails<TEntity>
{
public List<SortItem> SortItems { get; set; } = new();
}public class SortItem
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
}public enum SortDirection
{
ASC,
DESC
}Configure when changes are saved to the database:
public enum SaveChangeStrategy
{
PerOperation, // Save changes after each operation
PerUnitOfWork // Save changes only when unit of work completes
}Control repository generation for specific DbSet properties:
public class YourDbContext : DbContext
{
[Repository(CreateGenericRepository = false, CreateQueryRepository = true)]
public DbSet<ReadOnlyEntity> ReadOnlyEntities { get; set; }
[Repository(CreateGenericRepository = true, CreateQueryRepository = false)]
public DbSet<WriteOnlyEntity> WriteOnlyEntities { get; set; }
}// Get products with categories and suppliers
var products = await _queryRepository.GetListAsync(
relatedProperties: new List<Func<IQueryable<Product>, IIncludableQueryable<Product, object>>>
{
q => q.Include(p => p.Category),
q => q.Include(p => p.Supplier)
},
predicate: p => p.Price > 100,
sortingDetails: new SortingDetails<Product>
{
SortItems = new List<SortItem>
{
new() { PropertyName = "Name", Direction = SortDirection.ASC },
new() { PropertyName = "Price", Direction = SortDirection.DESC }
}
},
skip: 10,
take: 20
);// Get paginated products by category
var pagedProducts = await _queryRepository.GetListByPageAsync(
pageIndex: 0,
pageSize: 10,
predicate: p => p.CategoryId == categoryId,
sortingDetails: new SortingDetails<Product>
{
SortItems = new List<SortItem>
{
new() { PropertyName = "CreatedAt", Direction = SortDirection.DESC }
}
}
);
// Access pagination details
Console.WriteLine($"Total items: {pagedProducts.PagingDetails.TotalCount}");
Console.WriteLine($"Total pages: {pagedProducts.PagingDetails.TotalPages}");
Console.WriteLine($"Has next page: {pagedProducts.PagingDetails.HasNextPage}");public async Task TransferMoneyAsync(int fromAccountId, int toAccountId, decimal amount)
{
using var scope = await _unitOfWork.CreateScopeAsync();
try
{
var fromAccount = await _repository.GetForUpdateAsync(a => a.Id == fromAccountId);
var toAccount = await _repository.GetForUpdateAsync(a => a.Id == toAccountId);
fromAccount.Balance -= amount;
toAccount.Balance += amount;
await _repository.UpdateAsync(fromAccount);
await _repository.UpdateAsync(toAccount);
await scope.CompletAsync();
}
catch
{
// Transaction will be rolled back automatically
throw;
}
}Common.Repository/
βββ Common.Repository/ # Main library
β βββ EfCore/ # EF Core specific implementations
β β βββ Extensions/ # Service collection extensions
β β βββ Options/ # Configuration options
β βββ Repository/ # Repository interfaces and implementations
β β βββ EfCore/ # EF Core repository implementations
β βββ UnitOfWork/ # Unit of work interfaces and implementations
β β βββ EfCore/ # EF Core unit of work implementations
β βββ Lists/ # Pagination and sorting utilities
β β βββ Pagination/ # Paging classes
β β βββ Sorting/ # Sorting classes
β βββ Exceptions/ # Custom exceptions
βββ Sample/ # Example implementation
β βββ Sample.API/ # Web API example
β βββ Sample.Application/ # Application layer example
β βββ Sample.Domain/ # Domain entities example
β βββ Sample.Persistence/ # Data access layer example
βββ Common.Repository.sln # Solution file
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Mikheil berishvili - GitHub
- Entity Framework Core team for the excellent ORM
- .NET community for inspiration and best practices
- All contributors who help improve this library
Note: This library is designed for .NET 6.0+ and Entity Framework Core 6.0+. Make sure your project targets the appropriate framework version.