DKNet.EfCore.Repos
9.0.29
See the version list below for details.
dotnet add package DKNet.EfCore.Repos --version 9.0.29
NuGet\Install-Package DKNet.EfCore.Repos -Version 9.0.29
<PackageReference Include="DKNet.EfCore.Repos" Version="9.0.29" />
<PackageVersion Include="DKNet.EfCore.Repos" Version="9.0.29" />
<PackageReference Include="DKNet.EfCore.Repos" />
paket add DKNet.EfCore.Repos --version 9.0.29
#r "nuget: DKNet.EfCore.Repos, 9.0.29"
#:package DKNet.EfCore.Repos@9.0.29
#addin nuget:?package=DKNet.EfCore.Repos&version=9.0.29
#tool nuget:?package=DKNet.EfCore.Repos&version=9.0.29
DKNet.EfCore.Repos
Concrete implementations of the Repository pattern for Entity Framework Core, providing ready-to-use repository classes that implement the abstractions from DKNet.EfCore.Repos.Abstractions. This package includes generic repositories, automatic DI registration, and Mapster integration for projections.
Features
- Concrete Repository Implementations: Ready-to-use implementations of repository abstractions
- Generic Repository Support: Type-safe generic repositories for any entity
- Automatic DI Registration: Simple service collection extensions for dependency injection
- Mapster Integration: Built-in projection support with Mapster for efficient queries
- CQRS Implementation: Separate read and write repository implementations
- DbContext Integration: Direct Entity Framework Core integration with change tracking
- Transaction Support: Built-in transaction management for complex operations
- Performance Optimized: Efficient query execution with projection capabilities
Supported Frameworks
- .NET 9.0+
- Entity Framework Core 9.0+
- Mapster (for projections)
Installation
Install via NuGet Package Manager:
dotnet add package DKNet.EfCore.Repos
Or via Package Manager Console:
Install-Package DKNet.EfCore.Repos
Quick Start
Setup with Dependency Injection
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}
// Configure services
public void ConfigureServices(IServiceCollection services)
{
// Add DbContext
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// Add Mapster for projections
services.AddMapster();
// Add generic repositories
services.AddGenericRepositories<AppDbContext>();
}
Basic Repository Usage
public class ProductService
{
private readonly IRepository<Product> _productRepository;
private readonly IReadRepository<Customer> _customerRepository;
public ProductService(
IRepository<Product> productRepository,
IReadRepository<Customer> customerRepository)
{
_productRepository = productRepository;
_customerRepository = customerRepository;
}
public async Task<Product> CreateProductAsync(CreateProductRequest request)
{
var product = new Product(request.Name, request.Price, request.Category);
await _productRepository.AddAsync(product);
await _productRepository.SaveChangesAsync();
return product;
}
public async Task<List<ProductDto>> GetActiveProductsAsync()
{
// Efficient projection using Mapster
return await _productRepository
.GetDto<ProductDto>(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<Product?> GetProductByIdAsync(Guid id)
{
return await _productRepository.FindAsync(id);
}
}
public class ProductDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
Advanced CQRS Usage
public class OrderQueryService
{
private readonly IReadRepository<Order> _orderReadRepository;
public OrderQueryService(IReadRepository<Order> orderReadRepository)
{
_orderReadRepository = orderReadRepository;
}
public async Task<OrderSummaryDto?> GetOrderSummaryAsync(Guid orderId)
{
return await _orderReadRepository
.GetDto<OrderSummaryDto>(o => o.Id == orderId)
.FirstOrDefaultAsync();
}
public async Task<List<OrderListDto>> GetCustomerOrdersAsync(Guid customerId)
{
return await _orderReadRepository
.GetDto<OrderListDto>(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedDate)
.ToListAsync();
}
}
public class OrderCommandService
{
private readonly IWriteRepository<Order> _orderWriteRepository;
private readonly IReadRepository<Product> _productReadRepository;
public OrderCommandService(
IWriteRepository<Order> orderWriteRepository,
IReadRepository<Product> productReadRepository)
{
_orderWriteRepository = orderWriteRepository;
_productReadRepository = productReadRepository;
}
public async Task<Guid> CreateOrderAsync(CreateOrderRequest request)
{
using var transaction = await _orderWriteRepository.BeginTransactionAsync();
try
{
var order = new Order(request.CustomerId);
// Validate products exist
foreach (var item in request.Items)
{
var product = await _productReadRepository.FindAsync(item.ProductId);
if (product == null)
throw new InvalidOperationException($"Product {item.ProductId} not found");
order.AddItem(item.ProductId, item.Quantity, product.Price);
}
await _orderWriteRepository.AddAsync(order);
await _orderWriteRepository.SaveChangesAsync();
await transaction.CommitAsync();
return order.Id;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
Configuration
Mapster Configuration for Projections
public static class MappingConfig
{
public static void ConfigureMappings()
{
TypeAdapterConfig.GlobalSettings.Scan(typeof(Product).Assembly);
// Custom mapping configuration
TypeAdapterConfig<Product, ProductDto>
.NewConfig()
.Map(dest => dest.CategoryName, src => src.Category.Name)
.Map(dest => dest.IsOnSale, src => src.DiscountPercentage > 0);
TypeAdapterConfig<Order, OrderSummaryDto>
.NewConfig()
.Map(dest => dest.TotalAmount, src => src.Items.Sum(i => i.TotalPrice))
.Map(dest => dest.ItemCount, src => src.Items.Count);
}
}
// Register in Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMapster();
MappingConfig.ConfigureMappings();
services.AddGenericRepositories<AppDbContext>();
}
Custom Repository Implementation
public interface IProductRepository : IRepository<Product>
{
Task<IEnumerable<Product>> GetProductsByCategoryAsync(string category);
Task<bool> ExistsBySkuAsync(string sku);
Task<Product?> GetProductWithCategoryAsync(Guid id);
}
public class ProductRepository : Repository<Product>, IProductRepository
{
public ProductRepository(DbContext dbContext, IEnumerable<IMapper>? mappers = null)
: base(dbContext, mappers)
{
}
public async Task<IEnumerable<Product>> GetProductsByCategoryAsync(string category)
{
return await Gets()
.Where(p => p.Category == category && p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<bool> ExistsBySkuAsync(string sku)
{
return await Gets().AnyAsync(p => p.Sku == sku);
}
public async Task<Product?> GetProductWithCategoryAsync(Guid id)
{
return await Gets()
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id);
}
}
// Register custom repository
services.AddScoped<IProductRepository, ProductRepository>();
API Reference
Core Repository Classes
Repository<TEntity>
- Full repository implementation combining read and write operationsReadRepository<TEntity>
- Read-only repository implementationWriteRepository<TEntity>
- Write-only repository implementation
Setup Extensions
AddGenericRepositories<TDbContext>()
- Register all generic repositories with specified DbContext
Key Methods
Read Operations
Gets()
- Get IQueryable for building complex queriesGetDto<TModel>(filter?)
- Get projected DTOs with optional filteringFindAsync(id)
- Find entity by primary keyFindAsync(filter)
- Find first entity matching filter
Write Operations
AddAsync(entity)
- Add single entityAddRangeAsync(entities)
- Add multiple entitiesUpdateAsync(entity)
- Update entityDeleteAsync(entity)
- Delete entitySaveChangesAsync()
- Persist changes to database
Transaction Operations
BeginTransactionAsync()
- Start database transactionEntry(entity)
- Get EntityEntry for change tracking
Advanced Usage
Unit of Work Pattern with Generic Repositories
public interface IUnitOfWork
{
IRepository<Product> Products { get; }
IRepository<Customer> Customers { get; }
IRepository<Order> Orders { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
}
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private readonly IServiceProvider _serviceProvider;
public UnitOfWork(AppDbContext context, IServiceProvider serviceProvider)
{
_context = context;
_serviceProvider = serviceProvider;
}
public IRepository<Product> Products => _serviceProvider.GetRequiredService<IRepository<Product>>();
public IRepository<Customer> Customers => _serviceProvider.GetRequiredService<IRepository<Customer>>();
public IRepository<Order> Orders => _serviceProvider.GetRequiredService<IRepository<Order>>();
public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
=> _context.SaveChangesAsync(cancellationToken);
public Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default)
=> _context.Database.BeginTransactionAsync(cancellationToken);
}
Bulk Operations with Repositories
public class BulkOperationService
{
private readonly IWriteRepository<Product> _productRepository;
public BulkOperationService(IWriteRepository<Product> productRepository)
{
_productRepository = productRepository;
}
public async Task BulkUpdatePricesAsync(Dictionary<Guid, decimal> priceUpdates)
{
using var transaction = await _productRepository.BeginTransactionAsync();
try
{
var productIds = priceUpdates.Keys.ToList();
var products = await _productRepository.Gets()
.Where(p => productIds.Contains(p.Id))
.ToListAsync();
foreach (var product in products)
{
if (priceUpdates.TryGetValue(product.Id, out var newPrice))
{
product.UpdatePrice(newPrice);
}
}
await _productRepository.UpdateRangeAsync(products);
await _productRepository.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
public async Task BulkDeleteByIdsAsync(List<Guid> ids)
{
var entities = await _productRepository.Gets()
.Where(p => ids.Contains(p.Id))
.ToListAsync();
if (entities.Any())
{
await _productRepository.DeleteRangeAsync(entities);
await _productRepository.SaveChangesAsync();
}
}
}
Repository with Caching
public class CachedProductRepository : IReadRepository<Product>
{
private readonly IReadRepository<Product> _repository;
private readonly IMemoryCache _cache;
private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(15);
public CachedProductRepository(IReadRepository<Product> repository, IMemoryCache cache)
{
_repository = repository;
_cache = cache;
}
public async ValueTask<Product?> FindAsync(object keyValue, CancellationToken cancellationToken = default)
{
var cacheKey = $"product:{keyValue}";
if (_cache.TryGetValue(cacheKey, out Product? cachedProduct))
return cachedProduct;
var product = await _repository.FindAsync(keyValue, cancellationToken);
if (product != null)
{
_cache.Set(cacheKey, product, _cacheExpiry);
}
return product;
}
public IQueryable<Product> Gets() => _repository.Gets();
public IQueryable<TModel> GetDto<TModel>(Expression<Func<Product, bool>>? filter = null)
where TModel : class => _repository.GetDto<TModel>(filter);
// Implement other methods...
}
Integration with MediatR
public class GetProductQueryHandler : IRequestHandler<GetProductQuery, ProductDto?>
{
private readonly IReadRepository<Product> _repository;
public GetProductQueryHandler(IReadRepository<Product> repository)
{
_repository = repository;
}
public async Task<ProductDto?> Handle(GetProductQuery request, CancellationToken cancellationToken)
{
return await _repository
.GetDto<ProductDto>(p => p.Id == request.Id)
.FirstOrDefaultAsync(cancellationToken);
}
}
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Guid>
{
private readonly IWriteRepository<Product> _repository;
public CreateProductCommandHandler(IWriteRepository<Product> repository)
{
_repository = repository;
}
public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken)
{
var product = new Product(request.Name, request.Price, request.Category);
await _repository.AddAsync(product, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
return product.Id;
}
}
Performance Considerations
- Projections: Always use
GetDto<T>()
for read-only queries to minimize data transfer - Change Tracking: Write repositories automatically handle EF Core change tracking
- Query Optimization:
Gets()
returns IQueryable for efficient query composition - Batch Operations: Use range methods for bulk operations to reduce database round trips
- Mapster Integration: Efficient projections without manual mapping code
Best Practices
- Separation of Concerns: Use read repositories for queries, write repositories for modifications
- Transaction Management: Always use transactions for multi-entity operations
- Custom Repositories: Extend generic repositories for complex business logic
- Projection Usage: Prefer DTOs over entities for read operations
- Error Handling: Wrap transaction operations in try-catch blocks
Thread Safety
- Repository instances are designed to be used within a single request/operation scope
- DbContext instances should not be shared across threads
- Concurrent read operations are safe
- Write operations require external coordination for shared entities
Contributing
See the main CONTRIBUTING.md for guidelines on how to contribute to this project.
License
This project is licensed under the MIT License.
Related Packages
- DKNet.EfCore.Repos.Abstractions - Repository pattern abstractions
- DKNet.EfCore.Abstractions - Core entity abstractions
- DKNet.EfCore.Extensions - EF Core functionality extensions
- DKNet.EfCore.Specifications - Specification pattern support
Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- DKNet.EfCore.Extensions (>= 9.0.29)
- DKNet.EfCore.Repos.Abstractions (>= 9.0.29)
- Mapster (>= 7.4.0)
- X.PagedList.EF (>= 10.5.9)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on DKNet.EfCore.Repos:
Package | Downloads |
---|---|
DKNet.SlimBus.Extensions
Package Description |
|
DKNet.EfCore.Specifications
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
9.0.31 | 0 | 9/19/2025 |
9.0.30 | 29 | 9/18/2025 |
9.0.29 | 28 | 9/18/2025 |
9.0.28 | 38 | 9/17/2025 |
9.0.27 | 32 | 9/17/2025 |
9.0.26 | 40 | 9/16/2025 |
9.0.25 | 53 | 9/15/2025 |
9.0.24 | 49 | 9/15/2025 |
9.0.23 | 109 | 9/6/2025 |
9.0.22 | 162 | 9/3/2025 |
9.0.21 | 134 | 9/1/2025 |
9.0.20 | 150 | 7/15/2025 |
9.0.19 | 143 | 7/14/2025 |
9.0.18 | 143 | 7/14/2025 |
9.0.17 | 144 | 7/14/2025 |
9.0.16 | 123 | 7/11/2025 |
9.0.15 | 125 | 7/11/2025 |
9.0.14 | 127 | 7/11/2025 |
9.0.13 | 134 | 7/11/2025 |
9.0.12 | 152 | 7/8/2025 |
9.0.11 | 149 | 7/8/2025 |
9.0.10 | 142 | 7/7/2025 |
9.0.9 | 150 | 7/2/2025 |
9.0.8 | 146 | 7/2/2025 |
9.0.7 | 150 | 7/1/2025 |
9.0.6 | 148 | 6/30/2025 |
9.0.5 | 150 | 6/24/2025 |
9.0.4 | 147 | 6/24/2025 |
9.0.3 | 154 | 6/23/2025 |
9.0.2 | 147 | 6/23/2025 |
9.0.1 | 152 | 6/23/2025 |