Package Description
$ dotnet add package DKNet.EfCore.Repos.AbstractionsRepository pattern abstractions for Entity Framework Core, providing clean separation between read and write operations with strongly-typed interfaces. This package defines the contracts for data access operations following CQRS principles and Domain-Driven Design patterns.
Install via NuGet Package Manager:
dotnet add package DKNet.EfCore.Repos.Abstractions
Or via Package Manager Console:
Install-Package DKNet.EfCore.Repos.Abstractions
using DKNet.EfCore.Repos.Abstractions;
// Domain entity
public class Product : Entity<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
// Custom repository interface
public interface IProductRepository : IRepository<Product>
{
Task<IEnumerable<Product>> GetProductsByCategoryAsync(string category, CancellationToken cancellationToken = default);
Task<bool> ExistsByNameAsync(string name, CancellationToken cancellationToken = default);
}
// Service using repository
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task<Product> CreateProductAsync(string name, decimal price, string category)
{
// Check if product already exists
if (await _productRepository.ExistsByNameAsync(name))
throw new InvalidOperationException($"Product with name '{name}' already exists");
var product = new Product
{
Name = name,
Price = price,
Category = category
};
await _productRepository.AddAsync(product);
await _productRepository.SaveChangesAsync();
return product;
}
}
public class ProductQueryService
{
private readonly IReadRepository<Product> _readRepository;
public ProductQueryService(IReadRepository<Product> readRepository)
{
_readRepository = readRepository;
}
public async Task<List<ProductDto>> GetActiveProductsAsync()
{
// Use projection for efficient queries
return await _readRepository
.GetDto<ProductDto>(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
public async Task<Product?> GetProductByIdAsync(Guid id)
{
return await _readRepository.FindAsync(id);
}
public IQueryable<Product> GetProductsQuery()
{
// Return IQueryable for complex filtering
return _readRepository.Gets();
}
}
public class ProductDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
public class ProductManagementService
{
private readonly IWriteRepository<Product> _writeRepository;
public ProductManagementService(IWriteRepository<Product> writeRepository)
{
_writeRepository = writeRepository;
}
public async Task BulkUpdatePricesAsync(List<ProductPriceUpdate> updates)
{
using var transaction = await _writeRepository.BeginTransactionAsync();
try
{
foreach (var update in updates)
{
var product = await _writeRepository.FindAsync(update.ProductId);
if (product != null)
{
product.Price = update.NewPrice;
await _writeRepository.UpdateAsync(product);
}
}
await _writeRepository.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
public async Task AddProductsInBatchAsync(List<Product> products)
{
await _writeRepository.AddRangeAsync(products);
await _writeRepository.SaveChangesAsync();
}
}
public class ProductPriceUpdate
{
public Guid ProductId { get; set; }
public decimal NewPrice { get; set; }
}
using Microsoft.Extensions.DependencyInjection;
using DKNet.EfCore.Repos.Abstractions;
// Register repositories in DI container
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddRepositories(this IServiceCollection services)
{
// Register read-only repositories
services.AddScoped<IReadRepository<Product>, ProductReadRepository>();
services.AddScoped<IReadRepository<Customer>, CustomerReadRepository>();
// Register write repositories
services.AddScoped<IWriteRepository<Product>, ProductWriteRepository>();
services.AddScoped<IWriteRepository<Customer>, CustomerWriteRepository>();
// Register full repositories
services.AddScoped<IRepository<Product>, ProductRepository>();
services.AddScoped<IRepository<Customer>, CustomerRepository>();
// Register custom repositories
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
return services;
}
}
IRepository<TEntity> - Combined read and write operationsIReadRepository<TEntity> - Read-only operations and queriesIWriteRepository<TEntity> - Write operations and transaction managementGets() - Get IQueryable for entityGetDto<TModel>(Expression<Func<TEntity, bool>>?) - Get projection with optional filterFindAsync(object, CancellationToken) - Find entity by primary keyFindAsync(object[], CancellationToken) - Find entity by composite keyAnyAsync(Expression<Func<TEntity, bool>>, CancellationToken) - Check if any entity matches conditionCountAsync(Expression<Func<TEntity, bool>>, CancellationToken) - Count entities matching conditionAddAsync(TEntity, CancellationToken) - Add single entityAddRangeAsync(IEnumerable<TEntity>, CancellationToken) - Add multiple entitiesUpdateAsync(TEntity, CancellationToken) - Update single entityUpdateRangeAsync(IEnumerable<TEntity>, CancellationToken) - Update multiple entitiesDeleteAsync(TEntity, CancellationToken) - Delete single entityDeleteRangeAsync(IEnumerable<TEntity>, CancellationToken) - Delete multiple entitiesSaveChangesAsync(CancellationToken) - Persist changes to databaseBeginTransactionAsync(CancellationToken) - Begin database transactionEntry(TEntity) - Get EntityEntry for change trackingpublic interface IOrderRepository : IRepository<Order>
{
Task<IEnumerable<Order>> GetOrdersByStatusAsync(OrderStatus status, CancellationToken cancellationToken = default);
Task<Order?> GetOrderWithItemsAsync(Guid orderId, CancellationToken cancellationToken = default);
Task<decimal> GetTotalSalesAsync(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken = default);
}
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
public OrderService(IOrderRepository orderRepository, IProductRepository productRepository)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
}
public async Task<Order> CreateOrderAsync(Guid customerId, List<OrderItemRequest> items)
{
using var transaction = await _orderRepository.BeginTransactionAsync();
try
{
var order = new Order(customerId);
// Validate and add items
foreach (var item in items)
{
var product = await _productRepository.FindAsync(item.ProductId);
if (product == null)
throw new InvalidOperationException($"Product {item.ProductId} not found");
order.AddItem(item.ProductId, item.Quantity, product.Price);
}
await _orderRepository.AddAsync(order);
await _orderRepository.SaveChangesAsync();
await transaction.CommitAsync();
return order;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
public class ProductSpecificationService
{
private readonly IReadRepository<Product> _readRepository;
public ProductSpecificationService(IReadRepository<Product> readRepository)
{
_readRepository = readRepository;
}
public async Task<List<Product>> GetProductsAsync(ProductSearchCriteria criteria)
{
var query = _readRepository.Gets();
if (!string.IsNullOrEmpty(criteria.Category))
query = query.Where(p => p.Category == criteria.Category);
if (criteria.MinPrice.HasValue)
query = query.Where(p => p.Price >= criteria.MinPrice.Value);
if (criteria.MaxPrice.HasValue)
query = query.Where(p => p.Price <= criteria.MaxPrice.Value);
if (!string.IsNullOrEmpty(criteria.SearchTerm))
query = query.Where(p => p.Name.Contains(criteria.SearchTerm));
return await query
.OrderBy(p => p.Name)
.Skip(criteria.Skip)
.Take(criteria.Take)
.ToListAsync();
}
}
public class ProductSearchCriteria
{
public string? Category { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
public string? SearchTerm { get; set; }
public int Skip { get; set; } = 0;
public int Take { get; set; } = 50;
}
public interface IUnitOfWork
{
IProductRepository Products { get; }
ICustomerRepository Customers { get; }
IOrderRepository Orders { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
}
public class BusinessService
{
private readonly IUnitOfWork _unitOfWork;
public BusinessService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task ProcessOrderAsync(OrderRequest request)
{
using var transaction = await _unitOfWork.BeginTransactionAsync();
try
{
// Create customer if not exists
var customer = await _unitOfWork.Customers.FindAsync(request.CustomerId);
if (customer == null)
{
customer = new Customer(request.CustomerEmail);
await _unitOfWork.Customers.AddAsync(customer);
}
// Create order
var order = new Order(customer.Id);
await _unitOfWork.Orders.AddAsync(order);
// Update product inventory
foreach (var item in request.Items)
{
var product = await _unitOfWork.Products.FindAsync(item.ProductId);
if (product != null)
{
product.ReduceInventory(item.Quantity);
await _unitOfWork.Products.UpdateAsync(product);
}
}
await _unitOfWork.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
GetDto<T>() for efficient queries that only select needed columnsSee the main CONTRIBUTING.md for guidelines on how to contribute to this project.
This project is licensed under the MIT License.
Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.