Myth.Repository.EntityFramework 3.0.4

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Myth.Repository.EntityFramework --version 3.0.4
                    
NuGet\Install-Package Myth.Repository.EntityFramework -Version 3.0.4
                    
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="Myth.Repository.EntityFramework" Version="3.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Myth.Repository.EntityFramework" Version="3.0.4" />
                    
Directory.Packages.props
<PackageReference Include="Myth.Repository.EntityFramework" />
                    
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 Myth.Repository.EntityFramework --version 3.0.4
                    
#r "nuget: Myth.Repository.EntityFramework, 3.0.4"
                    
#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 Myth.Repository.EntityFramework@3.0.4
                    
#: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=Myth.Repository.EntityFramework&version=3.0.4
                    
Install as a Cake Addin
#tool nuget:?package=Myth.Repository.EntityFramework&version=3.0.4
                    
Install as a Cake Tool

Myth.Repository.EntityFramework

NuGet Version NuGet Version

License

pt-br en

A comprehensive .NET library that provides robust, feature-rich repository implementations for Entity Framework Core with Domain-Driven Design (DDD) patterns, Specification pattern support, and automatic dependency injection.

Table of Contents

⭐ Features

  • BaseContext: Abstract DbContext with automatic entity configuration discovery
  • Repository Implementations:
    • ReadRepositoryAsync<TEntity>: Read-only operations
    • WriteRepositoryAsync<TEntity>: Write operations (Add, Update, Remove)
    • ReadWriteRepositoryAsync<TEntity>: Combined read/write operations
  • Specification Pattern Support: Seamlessly integrate with Myth.Specification
  • Expression-Based Queries: Direct LINQ expression support
  • Unit of Work Pattern: Transaction management with savepoints
  • Automatic Dependency Injection: Zero-configuration repository registration
  • Pagination Support: Built-in paginated query results
  • Async/Await: Fully asynchronous API
  • Entity Attach/Detach: Fine-grained entity state management

📦 Installation

Install via NuGet Package Manager:

dotnet add package Myth.Repository.EntityFramework

Or via Package Manager Console:

Install-Package Myth.Repository.EntityFramework

🚀 Quick Start

1. Create Your DbContext

using Myth.Contexts;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : BaseContext {
    public ApplicationDbContext( DbContextOptions<ApplicationDbContext> options ) : base( options ) { }

    public DbSet<User> Users { get; set; }
    public DbSet<Product> Products { get; set; }
}

The BaseContext automatically discovers and applies all IEntityTypeConfiguration<T> implementations in your assembly.

2. Create Repository Interface

using Myth.Interfaces.Repositories.EntityFramework;

public interface IUserRepository : IReadWriteRepositoryAsync<User> {
    // Add custom methods if needed
    Task<User?> GetByEmailAsync( string email, CancellationToken cancellationToken = default );
}

3. Implement Repository

using Myth.Repositories.EntityFramework;

public class UserRepository : ReadWriteRepositoryAsync<User>, IUserRepository {

    public UserRepository( ApplicationDbContext context ) : base( context ) { }

    public async Task<User?> GetByEmailAsync( string email, CancellationToken cancellationToken = default ) {
        return await FirstOrDefaultAsync( u => u.Email == email, cancellationToken );
    }
}

4. Register in Startup

var builder = WebApplication.CreateBuilder( args );

// Register DbContext
builder.Services.AddDbContext<ApplicationDbContext>( options =>
    options.UseSqlServer( builder.Configuration.GetConnectionString( "DefaultConnection" ) ) );

// Automatically register ALL repositories
builder.Services.AddRepositories( );

var app = builder.BuildApp( ); // Use BuildApp() for cross-library dependency resolution
app.Run( );

5. Use in Your Services

public class UserService {
    private readonly IUserRepository _userRepository;

    public UserService( IUserRepository userRepository ) {
        _userRepository = userRepository;
    }

    public async Task<User?> GetUserByEmailAsync( string email ) {
        return await _userRepository.GetByEmailAsync( email );
    }

    public async Task CreateUserAsync( User user ) {
        await _userRepository.AddAsync( user );
        await _userRepository.SaveChangesAsync( );
    }
}

🎯 Core Concepts

BaseContext

The BaseContext is an abstract base class that extends DbContext and provides automatic entity configuration:

public abstract class BaseContext : DbContext {
    protected override void OnModelCreating( ModelBuilder modelBuilder ) {
        // Automatically applies all IEntityTypeConfiguration<T> from the assembly
        modelBuilder.ApplyConfigurationsFromAssembly( GetType( ).Assembly );
    }
}

Benefits:

  • No manual configuration registration
  • Convention-based entity mapping discovery
  • Cleaner, more maintainable code

📂 Repository Types

IReadRepositoryAsync<TEntity>

Read-only repository for query operations.

Core Methods:

// Basic queries
Task<IEnumerable<TEntity>> ToListAsync( CancellationToken cancellationToken = default );
IQueryable<TEntity> AsQueryable( );
IEnumerable<TEntity> AsEnumerable( );

// Filtered queries
IQueryable<TEntity> Where( Expression<Func<TEntity, bool>> predicate );
Task<IEnumerable<TEntity>> SearchAsync( Expression<Func<TEntity, bool>> filter, Expression<Func<TEntity, bool>>? orderBy = null, CancellationToken cancellationToken = default );

// Pagination
Task<IPaginated<TEntity>> SearchPaginatedAsync( Expression<Func<TEntity, bool>> predicate, int take = 0, int skip = 0, Expression<Func<TEntity, bool>>? orderBy = null, CancellationToken cancellationToken = default );
Task<IPaginated<TEntity>> SearchPaginatedAsync( Expression<Func<TEntity, bool>> predicate, Pagination pagination, Expression<Func<TEntity, bool>>? orderBy = null, CancellationToken cancellationToken = default );

// Single item retrieval
Task<TEntity?> FirstOrDefaultAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
Task<TEntity?> LastOrDefaultAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
Task<TEntity> FirstAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
Task<TEntity> LastAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

// Aggregate operations
Task<int> CountAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
Task<bool> AnyAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
Task<bool> AllAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

// Metadata
string? GetProviderName( );

Specification Pattern Support:

All methods have specification-based overloads:

Task<IEnumerable<TEntity>> SearchAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
Task<IPaginated<TEntity>> SearchPaginatedAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
IQueryable<TEntity> Where( ISpec<TEntity> specification );
Task<int> CountAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
// ... and more

IWriteRepositoryAsync<TEntity>

Write operations for creating, updating, and deleting entities.

Methods:

// Create
Task AddAsync( TEntity entity, CancellationToken cancellationToken = default );
Task AddRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );

// Update
Task UpdateAsync( TEntity entity, CancellationToken cancellationToken = default );
Task UpdateRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );

// Delete
Task RemoveAsync( TEntity entity, CancellationToken cancellationToken = default );
Task RemoveRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );

// Entity state management
Task AttachAsync( TEntity entity, CancellationToken cancellationToken = default );
Task AttachRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );

IReadWriteRepositoryAsync<TEntity>

Combines both read and write operations. This is the most commonly used repository type.

Usage:

public class ProductRepository : ReadWriteRepositoryAsync<Product>, IProductRepository {
    public ProductRepository( ApplicationDbContext context ) : base( context ) { }
}

🔧 Advanced Usage

Working with Specifications

The library integrates seamlessly with Myth.Specification:

using Myth.Specification;

// Define specifications
public static class UserSpecifications {
    public static ISpec<User> IsActive( this ISpec<User> spec ) {
        return spec.And( u => u.IsActive );
    }

    public static ISpec<User> HasRole( this ISpec<User> spec, string role ) {
        return spec.And( u => u.Role == role );
    }
}

// Use in repository
public class UserRepository : ReadWriteRepositoryAsync<User>, IUserRepository {
    public UserRepository( ApplicationDbContext context ) : base( context ) { }

    public async Task<IEnumerable<User>> GetActiveAdminsAsync( ) {
        var spec = SpecBuilder<User>.Create( )
            .IsActive( )
            .HasRole( "Admin" )
            .Order( u => u.Name );

        return await SearchAsync( spec );
    }

    public async Task<IPaginated<User>> GetActiveUsersPaginatedAsync( int page, int pageSize ) {
        var spec = SpecBuilder<User>.Create( )
            .IsActive( )
            .Order( u => u.CreatedAt )
            .Skip( ( page - 1 ) * pageSize )
            .Take( pageSize );

        return await SearchPaginatedAsync( spec );
    }
}

Pagination Examples

Using Expression-Based Pagination:

public async Task<IPaginated<Product>> GetProductsAsync( int page, int pageSize ) {
    var skip = ( page - 1 ) * pageSize;
    return await _productRepository.SearchPaginatedAsync(
        predicate: p => p.IsAvailable,
        take: pageSize,
        skip: skip,
        orderPredicate: p => p.Price
    );
}

Using Pagination Object:

public async Task<IPaginated<Product>> GetProductsAsync( Pagination pagination ) {
    return await _productRepository.SearchPaginatedAsync(
        predicate: p => p.IsAvailable,
        pagination: pagination,
        orderPredicate: p => p.Name
    );
}

Result Structure:

public interface IPaginated<T> {
    IEnumerable<T> Items { get; }
    int TotalItems { get; }
    int PageSize { get; }
    int CurrentPage { get; }
    int TotalPages { get; }
    bool HasPrevious { get; }
    bool HasNext { get; }
}

Custom Repository Methods

Extend base repositories with domain-specific methods:

public interface IOrderRepository : IReadWriteRepositoryAsync<Order> {
    Task<IEnumerable<Order>> GetOrdersByCustomerAsync( Guid customerId, CancellationToken cancellationToken = default );
    Task<Order?> GetOrderWithItemsAsync( Guid orderId, CancellationToken cancellationToken = default );
    Task<decimal> GetTotalRevenueAsync( DateTime from, DateTime to, CancellationToken cancellationToken = default );
}

public class OrderRepository : ReadWriteRepositoryAsync<Order>, IOrderRepository {

    public OrderRepository( ApplicationDbContext context ) : base( context ) { }

    public async Task<IEnumerable<Order>> GetOrdersByCustomerAsync( Guid customerId, CancellationToken cancellationToken = default ) {
        return await SearchAsync( o => o.CustomerId == customerId, o => o.OrderDate, cancellationToken );
    }

    public async Task<Order?> GetOrderWithItemsAsync( Guid orderId, CancellationToken cancellationToken = default ) {
        return await AsQueryable( )
            .Include( o => o.OrderItems )
            .ThenInclude( oi => oi.Product )
            .FirstOrDefaultAsync( o => o.Id == orderId, cancellationToken );
    }

    public async Task<decimal> GetTotalRevenueAsync( DateTime from, DateTime to, CancellationToken cancellationToken = default ) {
        var orders = await SearchAsync( o => o.OrderDate >= from && o.OrderDate <= to, null, cancellationToken );
        return orders.Sum( o => o.TotalAmount );
    }
}

🪄 Unit of Work Pattern

The IUnitOfWorkRepository provides transaction management capabilities.

Interface

public interface IUnitOfWorkRepository : IAsyncDisposable {
    Task BeginTransactionAsync( CancellationToken cancellationToken = default );
    Task CommitAsync( CancellationToken cancellationToken = default );
    Task RollbackAsync( CancellationToken cancellationToken = default );
    Task CreateSavepointAsync( string savepointName, CancellationToken cancellationToken = default );
    Task RollbackToSavepointAsync( string savepointName, CancellationToken cancellationToken = default );
    Task<int> SaveChangesAsync( CancellationToken cancellationToken = default );
    Task<int> ExecuteSqlAsync( string query, IEnumerable<object>? parameters = null, CancellationToken cancellationToken = default );
}

Implementation

// Create custom Unit of Work
public class ApplicationUnitOfWork : BaseUnitOfWorkRepository {
    public ApplicationUnitOfWork( ApplicationDbContext context ) : base( context ) { }
}

// Or use generic implementation
public class GenericUnitOfWork : UnitOfWorkRepository<ApplicationDbContext> {
    public GenericUnitOfWork( ApplicationDbContext context ) : base( context ) { }
}

Registration

// Option 1: Custom implementation
builder.Services.AddUnitOfWork<ApplicationUnitOfWork>( );

// Option 2: Generic implementation
builder.Services.AddUnitOfWorkForContext<ApplicationDbContext>( );

Usage Example

Basic Transaction:

public class OrderService {
    private readonly IOrderRepository _orderRepository;
    private readonly IInventoryRepository _inventoryRepository;
    private readonly IUnitOfWorkRepository _unitOfWork;

    public OrderService( IOrderRepository orderRepository, IInventoryRepository inventoryRepository, IUnitOfWorkRepository unitOfWork ) {
        _orderRepository = orderRepository;
        _inventoryRepository = inventoryRepository;
        _unitOfWork = unitOfWork;
    }

    public async Task<Result> CreateOrderAsync( CreateOrderRequest request ) {
        await _unitOfWork.BeginTransactionAsync( );

        try {
            // Create order
            var order = new Order { CustomerId = request.CustomerId, OrderDate = DateTime.UtcNow };
            await _orderRepository.AddAsync( order );
            await _unitOfWork.SaveChangesAsync( );

            // Update inventory
            foreach ( var item in request.Items ) {
                var inventory = await _inventoryRepository.FirstOrDefaultAsync( i => i.ProductId == item.ProductId );
                if ( inventory == null || inventory.Quantity < item.Quantity )
                    throw new InvalidOperationException( "Insufficient inventory" );

                inventory.Quantity -= item.Quantity;
                await _inventoryRepository.UpdateAsync( inventory );
            }
            await _unitOfWork.SaveChangesAsync( );

            // Commit transaction
            await _unitOfWork.CommitAsync( );
            return Result.Success( );
        }
        catch ( Exception ex ) {
            await _unitOfWork.RollbackAsync( );
            return Result.Failure( ex.Message );
        }
    }
}

Transaction with Savepoints:

public async Task<Result> ComplexOperationAsync( ) {
    await _unitOfWork.BeginTransactionAsync( );

    try {
        // Step 1: Create user
        await CreateUserAsync( );
        await _unitOfWork.SaveChangesAsync( );
        await _unitOfWork.CreateSavepointAsync( "AfterUserCreation" );

        // Step 2: Create profile (might fail)
        try {
            await CreateUserProfileAsync( );
            await _unitOfWork.SaveChangesAsync( );
        }
        catch {
            // Rollback to savepoint, keeping user creation
            await _unitOfWork.RollbackToSavepointAsync( "AfterUserCreation" );
            await CreateDefaultProfileAsync( );
            await _unitOfWork.SaveChangesAsync( );
        }

        await _unitOfWork.CommitAsync( );
        return Result.Success( );
    }
    catch ( Exception ex ) {
        await _unitOfWork.RollbackAsync( );
        return Result.Failure( ex.Message );
    }
}

🚀 Dependency Injection

Automatic Registration

The library provides intelligent automatic registration:

// Registers ALL repository implementations from application assemblies
builder.Services.AddRepositories( );

Registration Rules:

  • Scoped lifetime by default (recommended for repositories)
  • Automatically excludes test types (Test, Mock, Fake, Stub)
  • Automatically excludes generic type definitions
  • Matches repositories to interfaces by naming convention

Registration from Specific Assembly

// Register repositories from a specific assembly
builder.Services.AddRepositoriesFromAssembly( typeof( UserRepository ).Assembly );

// With custom lifetime
builder.Services.AddRepositoriesFromAssembly( typeof( UserRepository ).Assembly, ServiceLifetime.Transient );

Manual Registration

// Register specific repository manually
builder.Services.AddRepository<IUserRepository, UserRepository>( );

// With custom lifetime
builder.Services.AddRepository<IUserRepository, UserRepository>( ServiceLifetime.Transient );

Unit of Work Registration

// Register custom Unit of Work implementation
builder.Services.AddUnitOfWork<ApplicationUnitOfWork>( );

// Or register generic Unit of Work for specific context
builder.Services.AddUnitOfWorkForContext<ApplicationDbContext>( );

Complete Setup Example

var builder = WebApplication.CreateBuilder( args );

// Database configuration
builder.Services.AddDbContext<ApplicationDbContext>( options => {
    options.UseSqlServer( builder.Configuration.GetConnectionString( "DefaultConnection" ) );
    options.EnableSensitiveDataLogging( builder.Environment.IsDevelopment( ) );
    options.EnableDetailedErrors( builder.Environment.IsDevelopment( ) );
} );

// Repository registration
builder.Services.AddRepositories( ); // Auto-register all repositories

// Unit of Work registration
builder.Services.AddUnitOfWorkForContext<ApplicationDbContext>( );

// Build app with cross-library dependency resolution
var app = builder.BuildApp( );

app.Run( );

📝 Best Practices

  1. Use Repository Interfaces: Always depend on interfaces, not concrete implementations
  2. Keep Repositories Thin: Complex business logic belongs in services, not repositories
  3. Leverage Specifications: Use Myth.Specification for reusable query logic
  4. Use Unit of Work for Transactions: When operations span multiple repositories
  5. Async All The Way: Always use async/await for database operations
  6. Dispose Properly: Repositories implement IAsyncDisposable when needed
  7. Use BuildApp(): Replace builder.Build() with builder.BuildApp() for cross-library integration
  • Myth.Repository: Base repository interfaces and abstractions
  • Myth.Specification: Specification pattern implementation for query building
  • Myth.Commons: Common utilities and extensions

📄 License

Licensed under the Apache 2.0 License. See LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  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 Myth.Repository.EntityFramework:

Package Downloads
Harpy.Domain

Basis for the domain layer of the Harpy Framework

Harpy.Context

Basis for the context layer of the Harpy Framework

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.5-preview.3 32 11/4/2025
3.0.5-preview.2 42 11/4/2025
3.0.5-preview.1 34 11/4/2025
3.0.4 87 11/3/2025
3.0.4-preview.19 48 11/2/2025
3.0.4-preview.17 46 11/1/2025
3.0.4-preview.16 49 11/1/2025
3.0.4-preview.15 56 10/31/2025
3.0.4-preview.14 126 10/31/2025
3.0.4-preview.13 125 10/30/2025
3.0.4-preview.12 118 10/23/2025
3.0.4-preview.11 115 10/23/2025
3.0.4-preview.10 112 10/23/2025
3.0.4-preview.9 112 10/23/2025
3.0.4-preview.8 118 10/22/2025
3.0.4-preview.6 111 10/21/2025
3.0.4-preview.5 111 10/21/2025
3.0.4-preview.4 114 10/20/2025
3.0.4-preview.3 114 10/20/2025
3.0.4-preview.2 36 10/18/2025
3.0.4-preview.1 119 10/7/2025
3.0.3 210 8/30/2025
3.0.2 107 8/23/2025
3.0.2-preview.4 128 8/21/2025
3.0.2-preview.3 101 8/16/2025
3.0.2-preview.1 80 5/23/2025
3.0.1 3,340 3/12/2025
3.0.1-preview.2 150 3/11/2025
3.0.1-preview.1 86 2/5/2025
3.0.0.2-preview 122 12/10/2024
3.0.0.1-preview 145 6/29/2024
3.0.0 4,686 12/10/2024
3.0.0-preview 148 6/28/2024
2.0.0.17 1,981 4/17/2024
2.0.0.16 3,421 12/15/2023
2.0.0.15 209 12/15/2023
2.0.0.11 3,802 8/11/2022
2.0.0.10 2,687 7/20/2022
2.0.0.9 2,762 7/15/2022
2.0.0.8 2,793 7/12/2022
2.0.0.7 2,719 6/20/2022
2.0.0.6 2,801 5/23/2022
2.0.0.5 2,761 5/18/2022
2.0.0 2,873 2/17/2022