DKNet.EfCore.Repos 9.0.29

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DKNet.EfCore.Repos" Version="9.0.29" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DKNet.EfCore.Repos" Version="9.0.29" />
                    
Directory.Packages.props
<PackageReference Include="DKNet.EfCore.Repos" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DKNet.EfCore.Repos --version 9.0.29
                    
#r "nuget: DKNet.EfCore.Repos, 9.0.29"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DKNet.EfCore.Repos@9.0.29
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DKNet.EfCore.Repos&version=9.0.29
                    
Install as a Cake Addin
#tool nuget:?package=DKNet.EfCore.Repos&version=9.0.29
                    
Install as a Cake Tool

DKNet.EfCore.Repos

NuGet NuGet Downloads .NET License

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 operations
  • ReadRepository<TEntity> - Read-only repository implementation
  • WriteRepository<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 queries
  • GetDto<TModel>(filter?) - Get projected DTOs with optional filtering
  • FindAsync(id) - Find entity by primary key
  • FindAsync(filter) - Find first entity matching filter
Write Operations
  • AddAsync(entity) - Add single entity
  • AddRangeAsync(entities) - Add multiple entities
  • UpdateAsync(entity) - Update entity
  • DeleteAsync(entity) - Delete entity
  • SaveChangesAsync() - Persist changes to database
Transaction Operations
  • BeginTransactionAsync() - Start database transaction
  • Entry(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.


Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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