Myth.Repository 4.2.0

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

<img style="float: right;" src="myth-repository-logo.png" alt="drawing" width="250"/>

Myth.Repository

NuGet Version NuGet Version

License

pt-br en

Myth.Repository is a .NET library that provides a clean, standardized set of repository interfaces for data access. It promotes separation of concerns through read/write segregation, integrates seamlessly with the Specification pattern, and supports pagination out of the box. Perfect for building maintainable, testable, and domain-driven applications.

Features

  • Repository Interfaces: Standard contracts for data access operations
  • Read/Write Segregation: Separate interfaces for read and write operations (CQRS-friendly)
  • Specification Pattern Integration: First-class support for ISpec<T> from Myth.Specification
  • Expression Support: Works with both specifications and LINQ expressions
  • Pagination: Built-in pagination support with IPaginated<T> results
  • Async-First: All operations are asynchronous with cancellation token support
  • Query Operations: Rich querying capabilities (Where, First, Last, Any, All, Count)
  • Batch Operations: Support for bulk inserts, updates, and deletes
  • IQueryable Support: Access to underlying queryable for advanced scenarios
  • IAsyncDisposable: Proper resource cleanup with async disposal

Installation

dotnet add package Myth.Repository

For Entity Framework Core implementation:

dotnet add package Myth.Repository.EntityFramework

Quick Start

Define Your Entity

public class Product {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool IsActive { get; set; }
    public string Category { get; set; }
}

Create Repository Interface

using Myth.Interfaces.Repositories.Base;

public interface IProductRepository : IReadWriteRepositoryAsync<Product> {
    // Add custom methods if needed
}

Implement Repository (Entity Framework Example)

using Myth.Repository.EntityFramework.Repositories;

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

Use in Your Application

public class ProductService {
    private readonly IProductRepository _repository;

    public ProductService( IProductRepository repository ) {
        _repository = repository;
    }

    public async Task<Product?> GetProductByIdAsync( Guid id, CancellationToken cancellationToken ) {
        return await _repository.FirstOrDefaultAsync( p => p.Id == id, cancellationToken );
    }

    public async Task<IEnumerable<Product>> GetActiveProductsAsync( CancellationToken cancellationToken ) {
        return await _repository.SearchAsync( p => p.IsActive, cancellationToken );
    }

    public async Task CreateProductAsync( Product product, CancellationToken cancellationToken ) {
        await _repository.AddAsync( product, cancellationToken );
    }
}

Core Interfaces

IRepository

Base marker interface for all repository types.

public interface IRepository { }

IReadRepositoryAsync<TEntity>

Provides read-only operations for querying data.

public interface IReadRepositoryAsync<TEntity> : IRepository, IAsyncDisposable {
    // Queryable access
    IQueryable<TEntity> Where( ISpec<TEntity> specification );
    IQueryable<TEntity> Where( Expression<Func<TEntity, bool>> predicate );
    IQueryable<TEntity> AsQueryable( );
    IEnumerable<TEntity> AsEnumerable( );

    // Search operations
    Task<IEnumerable<TEntity>> SearchAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<IEnumerable<TEntity>> SearchAsync( Expression<Func<TEntity, bool>> filterPredicate, Expression<Func<TEntity, bool>>? orderPredicate = null, CancellationToken cancellationToken = default );

    // Paginated search
    Task<IPaginated<TEntity>> SearchPaginatedAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<IPaginated<TEntity>> SearchPaginatedAsync( Expression<Func<TEntity, bool>> filterPredicate, Pagination pagination, Expression<Func<TEntity, bool>>? orderPredicate = null, CancellationToken cancellationToken = default );

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

    Task<TEntity?> LastOrDefaultAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<TEntity?> LastOrDefaultAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );
    Task<TEntity> LastAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<TEntity> LastAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

    // Aggregation operations
    Task<int> CountAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<int> CountAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

    Task<bool> AnyAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<bool> AnyAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

    Task<bool> AllAsync( ISpec<TEntity> specification, CancellationToken cancellationToken = default );
    Task<bool> AllAsync( Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default );

    // Get all
    Task<IEnumerable<TEntity>> ToListAsync( CancellationToken cancellationToken = default );
}

IWriteRepositoryAsync<TEntity>

Provides write operations for modifying data.

public interface IWriteRepositoryAsync<TEntity> : IRepository, IAsyncDisposable {
    // Single operations
    Task AddAsync( TEntity entity, CancellationToken cancellationToken = default );
    Task UpdateAsync( TEntity entity, CancellationToken cancellationToken = default );
    Task RemoveAsync( TEntity entity, CancellationToken cancellationToken = default );

    // Batch operations
    Task AddRangeAsync( IEnumerable<TEntity> entity, CancellationToken cancellationToken = default );
    Task UpdateRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );
    Task RemoveRangeAsync( IEnumerable<TEntity> entities, CancellationToken cancellationToken = default );
}

IReadWriteRepositoryAsync<TEntity>

Combines both read and write operations in a single interface.

public interface IReadWriteRepositoryAsync<TEntity> : IReadRepositoryAsync<TEntity>, IWriteRepositoryAsync<TEntity>, IAsyncDisposable { }

Pagination Support

IPaginated<T>

Interface representing paginated results:

public interface IPaginated<T> {
    int PageNumber { get; }       // Current page number (1-based)
    int PageSize { get; }          // Number of items per page
    int TotalPages { get; }        // Total number of pages
    int TotalItems { get; }        // Total number of items across all pages
    IEnumerable<T> Items { get; }  // Items in current page
}

Pagination Value Object

public class Pagination {
    public int PageNumber { get; set; }  // Default: 1
    public int PageSize { get; set; }     // Default: 10

    // Static helpers
    public static readonly Pagination Default = new( 1, 10 );
    public static readonly Pagination All = new( -1, -1 );
}

Extension Method

using Myth.Extensions;

// Convert any IEnumerable to paginated result
var items = new List<Product> { /* ... */ };
var paginated = items.AsPaginated( pageSize: 20, skip: 0 );

// Or with Pagination object
var pagination = new Pagination( pageNumber: 2, pageSize: 20 );
var paginated = items.AsPaginated( pagination );

Usage Examples

Basic CRUD Operations

public class ProductService {
    private readonly IProductRepository _repository;

    public ProductService( IProductRepository repository ) {
        _repository = repository;
    }

    public async Task CreateAsync( Product product, CancellationToken ct ) {
        await _repository.AddAsync( product, ct );
    }

    public async Task CreateManyAsync( IEnumerable<Product> products, CancellationToken ct ) {
        await _repository.AddRangeAsync( products, ct );
    }

    public async Task UpdateAsync( Product product, CancellationToken ct ) {
        await _repository.UpdateAsync( product, ct );
    }

    public async Task DeleteAsync( Product product, CancellationToken ct ) {
        await _repository.RemoveAsync( product, ct );
    }
}

Querying with Expressions

public class ProductService {
    private readonly IProductRepository _repository;

    public async Task<Product?> GetByIdAsync( Guid id, CancellationToken ct ) {
        return await _repository.FirstOrDefaultAsync( p => p.Id == id, ct );
    }

    public async Task<IEnumerable<Product>> GetByCategoryAsync( string category, CancellationToken ct ) {
        return await _repository.SearchAsync( p => p.Category == category, ct );
    }

    public async Task<int> CountActiveProductsAsync( CancellationToken ct ) {
        return await _repository.CountAsync( p => p.IsActive, ct );
    }

    public async Task<bool> HasExpensiveProductsAsync( CancellationToken ct ) {
        return await _repository.AnyAsync( p => p.Price > 1000, ct );
    }

    public async Task<bool> AreAllActiveAsync( CancellationToken ct ) {
        return await _repository.AllAsync( p => p.IsActive, ct );
    }
}

Querying with Specifications

using Myth.Specification;

// Define specifications
public static class ProductSpecifications {
    public static ISpec<Product> IsActive( this ISpec<Product> spec ) {
        return spec.And( p => p.IsActive );
    }

    public static ISpec<Product> InCategory( this ISpec<Product> spec, string category ) {
        return spec.And( p => p.Category == category );
    }

    public static ISpec<Product> PriceRange( this ISpec<Product> spec, decimal min, decimal max ) {
        return spec.And( p => p.Price >= min && p.Price <= max );
    }

    public static ISpec<Product> OrderByName( this ISpec<Product> spec ) {
        return spec.Order( p => p.Name );
    }
}

// Use in repository
public class ProductService {
    private readonly IProductRepository _repository;

    public async Task<IEnumerable<Product>> GetActiveProductsInCategoryAsync(
        string category,
        CancellationToken ct ) {

        var spec = SpecBuilder<Product>.Create( )
            .IsActive( )
            .InCategory( category )
            .OrderByName( );

        return await _repository.SearchAsync( spec, ct );
    }

    public async Task<IEnumerable<Product>> GetAffordableProductsAsync(
        decimal maxPrice,
        CancellationToken ct ) {

        var spec = SpecBuilder<Product>.Create( )
            .IsActive( )
            .PriceRange( 0, maxPrice )
            .OrderByName( );

        return await _repository.SearchAsync( spec, ct );
    }
}

Pagination

public class ProductService {
    private readonly IProductRepository _repository;

    // Paginate with specification
    public async Task<IPaginated<Product>> GetProductsPageAsync(
        int pageNumber,
        int pageSize,
        CancellationToken ct ) {

        var pagination = new Pagination( pageNumber, pageSize );

        var spec = SpecBuilder<Product>.Create( )
            .IsActive( )
            .OrderByName( )
            .Skip( (pageNumber - 1) * pageSize )
            .Take( pageSize );

        return await _repository.SearchPaginatedAsync( spec, ct );
    }

    // Paginate with expression
    public async Task<IPaginated<Product>> GetProductsByCategoryPageAsync(
        string category,
        Pagination pagination,
        CancellationToken ct ) {

        return await _repository.SearchPaginatedAsync(
            filterPredicate: p => p.Category == category && p.IsActive,
            pagination: pagination,
            orderPredicate: p => p.Name,
            cancellationToken: ct );
    }

    // Use paginated results
    public async Task DisplayProductsAsync( CancellationToken ct ) {
        var result = await GetProductsPageAsync( pageNumber: 1, pageSize: 20, ct );

        Console.WriteLine( $"Page {result.PageNumber} of {result.TotalPages}" );
        Console.WriteLine( $"Total items: {result.TotalItems}" );

        foreach ( var product in result.Items ) {
            Console.WriteLine( $"- {product.Name}: ${product.Price}" );
        }
    }
}

Advanced Querying with IQueryable

public class ProductService {
    private readonly IProductRepository _repository;

    public async Task<IEnumerable<ProductSummary>> GetProductSummariesAsync( CancellationToken ct ) {
        var queryable = _repository
            .AsQueryable( )
            .Where( p => p.IsActive )
            .GroupBy( p => p.Category )
            .Select( g => new ProductSummary {
                Category = g.Key,
                Count = g.Count( ),
                AveragePrice = g.Average( p => p.Price )
            } );

        return await queryable.ToListAsync( ct );
    }
}

Read/Write Segregation (CQRS)

// Read-only service
public class ProductQueryService {
    private readonly IReadRepositoryAsync<Product> _repository;

    public ProductQueryService( IReadRepositoryAsync<Product> repository ) {
        _repository = repository;
    }

    public async Task<IEnumerable<Product>> GetAllAsync( CancellationToken ct ) {
        return await _repository.ToListAsync( ct );
    }
}

// Write-only service
public class ProductCommandService {
    private readonly IWriteRepositoryAsync<Product> _repository;

    public ProductCommandService( IWriteRepositoryAsync<Product> repository ) {
        _repository = repository;
    }

    public async Task CreateAsync( Product product, CancellationToken ct ) {
        await _repository.AddAsync( product, ct );
    }
}

Integration with Other Myth Libraries

With Myth.Specification

var spec = SpecBuilder<Product>.Create( )
    .IsActive( )
    .InCategory( "Electronics" )
    .PriceRange( 100, 500 )
    .OrderByName( )
    .Take( 50 );

var products = await _repository.SearchAsync( spec, cancellationToken );

With Myth.Flow.Actions (CQRS)

public class GetProductsQueryHandler : IQueryHandler<GetProductsQuery, IEnumerable<Product>> {
    private readonly IReadRepositoryAsync<Product> _repository;

    public async Task<QueryResult<IEnumerable<Product>>> HandleAsync(
        GetProductsQuery query,
        CancellationToken cancellationToken ) {

        var products = await _repository.SearchAsync(
            p => p.Category == query.Category,
            cancellationToken );

        return QueryResult<IEnumerable<Product>>.Success( products );
    }
}

With Myth.Guard (Validation)

public class ProductCommandService {
    private readonly IWriteRepositoryAsync<Product> _repository;
    private readonly IValidator _validator;

    public async Task CreateAsync( Product product, CancellationToken ct ) {
        await _validator.ValidateAsync( product, ValidationContextKey.Create, ct );
        await _repository.AddAsync( product, ct );
    }
}

Architecture Patterns

Domain-Driven Design (DDD)

// Aggregate Root
public class Order : IAggregateRoot {
    public Guid Id { get; private set; }
    private List<OrderItem> _items = new( );
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly( );

    public void AddItem( Product product, int quantity ) {
        _items.Add( new OrderItem( product, quantity ) );
    }
}

// Repository in domain layer (interface)
public interface IOrderRepository : IReadWriteRepositoryAsync<Order> {
    Task<Order?> GetByIdWithItemsAsync( Guid id, CancellationToken ct );
}

// Implementation in infrastructure layer
public class OrderRepository : ReadWriteRepositoryAsync<Order>, IOrderRepository {
    public OrderRepository( DbContext context ) : base( context ) { }

    public async Task<Order?> GetByIdWithItemsAsync( Guid id, CancellationToken ct ) {
        return await AsQueryable( )
            .Include( o => o.Items )
            .FirstOrDefaultAsync( o => o.Id == id, ct );
    }
}

Unit of Work Pattern

See Myth.Repository.EntityFramework for IUnitOfWorkRepository implementation.

public class OrderService {
    private readonly IUnitOfWorkRepository _unitOfWork;

    public async Task ProcessOrderAsync( Order order, CancellationToken ct ) {
        await _unitOfWork.BeginTransactionAsync( ct );

        try {
            await _unitOfWork.AddAsync( order, ct );
            await _unitOfWork.SaveChangesAsync( ct );
            await _unitOfWork.CommitTransactionAsync( ct );
        }
        catch {
            await _unitOfWork.RollbackTransactionAsync( ct );
            throw;
        }
    }
}

Best Practices

  1. Use Specifications for Complex Queries: Encapsulate business rules in specification extensions
  2. Leverage Read/Write Segregation: Use separate interfaces when following CQRS patterns
  3. Always Use Cancellation Tokens: Support graceful cancellation in long-running operations
  4. Dispose Repositories Properly: Use await using or dependency injection for automatic disposal
  5. Paginate Large Result Sets: Use SearchPaginatedAsync to avoid loading too much data
  6. Keep Repositories Thin: Move business logic to services or domain entities
  7. Use IQueryable Sparingly: Prefer specifications and expressions for better testability
  8. Implement Custom Methods: Add domain-specific methods to repository interfaces when needed

Testing

public class ProductServiceTests {
    private readonly Mock<IProductRepository> _mockRepository;
    private readonly ProductService _service;

    public ProductServiceTests( ) {
        _mockRepository = new Mock<IProductRepository>( );
        _service = new ProductService( _mockRepository.Object );
    }

    [Fact]
    public async Task GetByIdAsync_ShouldReturnProduct_WhenExists( ) {
        var productId = Guid.NewGuid( );
        var product = new Product { Id = productId, Name = "Test" };

        _mockRepository
            .Setup( r => r.FirstOrDefaultAsync( It.IsAny<Expression<Func<Product, bool>>>( ), default ) )
            .ReturnsAsync( product );

        var result = await _service.GetByIdAsync( productId, default );

        result.Should( ).NotBeNull( );
        result.Id.Should( ).Be( productId );
    }
}
  • Myth.Repository.EntityFramework: Entity Framework Core implementation with Unit of Work support
  • Myth.Specification: Build complex, reusable query specifications
  • Myth.Commons: Value objects and pagination support (IPaginated<T>)

License

This project is licensed under the Apache License 2.0 - see the LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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:

Package Downloads
Myth.Repository.EntityFramework

Entity Framework Core implementations of repository pattern with Unit of Work, specification support, expression handling, and transaction management for robust data access with EF Core.

Packs.Template.BaseApi

Basis for any Packs API

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.2.1-preview.1 609 12/2/2025
4.2.0 409 11/30/2025
4.2.0-preview.1 60 11/29/2025
4.1.0 167 11/27/2025
4.1.0-preview.3 127 11/27/2025
4.1.0-preview.2 121 11/27/2025
4.1.0-preview.1 124 11/26/2025
4.0.1 157 11/22/2025
4.0.1-preview.8 148 11/22/2025
4.0.1-preview.7 151 11/22/2025
4.0.1-preview.6 132 11/22/2025
4.0.1-preview.5 196 11/21/2025
4.0.1-preview.4 203 11/21/2025
4.0.1-preview.3 210 11/21/2025
4.0.1-preview.2 230 11/21/2025
4.0.1-preview.1 249 11/21/2025
4.0.0 390 11/20/2025
4.0.0-preview.3 341 11/19/2025
4.0.0-preview.2 89 11/15/2025
4.0.0-preview.1 114 11/15/2025
3.10.0 172 11/15/2025
3.0.5-preview.15 149 11/14/2025
3.0.5-preview.14 218 11/12/2025
3.0.5-preview.13 224 11/12/2025
3.0.5-preview.12 222 11/11/2025
3.0.5-preview.11 227 11/11/2025
3.0.5-preview.10 223 11/11/2025
3.0.5-preview.9 212 11/10/2025
3.0.5-preview.8 89 11/8/2025
3.0.5-preview.7 85 11/8/2025
3.0.5-preview.6 88 11/8/2025
3.0.5-preview.5 89 11/8/2025
3.0.5-preview.4 62 11/7/2025
3.0.5-preview.3 133 11/4/2025
3.0.5-preview.2 138 11/4/2025
3.0.5-preview.1 133 11/4/2025
3.0.4 222 11/3/2025
3.0.4-preview.19 81 11/2/2025
3.0.4-preview.17 73 11/1/2025
3.0.4-preview.16 75 11/1/2025
3.0.4-preview.15 75 10/31/2025
3.0.4-preview.14 132 10/31/2025
3.0.4-preview.13 135 10/30/2025
3.0.4-preview.12 127 10/23/2025
3.0.4-preview.11 122 10/23/2025
3.0.4-preview.10 118 10/23/2025
3.0.4-preview.9 116 10/23/2025
3.0.4-preview.8 124 10/22/2025
3.0.4-preview.6 118 10/21/2025
3.0.4-preview.5 125 10/21/2025
3.0.4-preview.4 117 10/20/2025
3.0.4-preview.3 122 10/20/2025
3.0.4-preview.2 46 10/18/2025
3.0.4-preview.1 123 10/7/2025
3.0.3 252 8/30/2025
3.0.2 143 8/23/2025
3.0.2-preview.4 129 8/21/2025
3.0.2-preview.3 99 8/16/2025
3.0.2-preview.1 81 5/23/2025
3.0.1 4,926 3/12/2025
3.0.1-preview.2 156 3/11/2025
3.0.1-preview.1 105 2/5/2025
3.0.0.2-preview 167 12/10/2024
3.0.0.1-preview 202 6/29/2024
3.0.0 4,698 12/10/2024
3.0.0-preview 187 6/28/2024
2.0.0.17 2,255 4/17/2024
2.0.0.16 15,135 12/15/2023
2.0.0.15 267 12/15/2023
2.0.0.11 5,482 8/11/2022
2.0.0.10 2,775 7/20/2022
2.0.0.9 2,851 7/15/2022
2.0.0.8 2,883 7/12/2022
2.0.0.7 2,800 6/20/2022
2.0.0.6 2,902 5/23/2022
2.0.0.5 2,869 5/18/2022
2.0.0 2,978 2/17/2022