Myth.Repository
4.2.0
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
<PackageReference Include="Myth.Repository" Version="4.2.0" />
<PackageVersion Include="Myth.Repository" Version="4.2.0" />
<PackageReference Include="Myth.Repository" />
paket add Myth.Repository --version 4.2.0
#r "nuget: Myth.Repository, 4.2.0"
#:package Myth.Repository@4.2.0
#addin nuget:?package=Myth.Repository&version=4.2.0
#tool nuget:?package=Myth.Repository&version=4.2.0
<img style="float: right;" src="myth-repository-logo.png" alt="drawing" width="250"/>
Myth.Repository
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
- Use Specifications for Complex Queries: Encapsulate business rules in specification extensions
- Leverage Read/Write Segregation: Use separate interfaces when following CQRS patterns
- Always Use Cancellation Tokens: Support graceful cancellation in long-running operations
- Dispose Repositories Properly: Use
await usingor dependency injection for automatic disposal - Paginate Large Result Sets: Use
SearchPaginatedAsyncto avoid loading too much data - Keep Repositories Thin: Move business logic to services or domain entities
- Use IQueryable Sparingly: Prefer specifications and expressions for better testability
- 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 );
}
}
Related Packages
- 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 | Versions 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. |
-
net10.0
- Myth.Commons (>= 4.2.0)
- Myth.Specification (>= 4.2.0)
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 |