RICH.Persistence
1.1.1
dotnet add package RICH.Persistence --version 1.1.1
NuGet\Install-Package RICH.Persistence -Version 1.1.1
<PackageReference Include="RICH.Persistence" Version="1.1.1" />
<PackageVersion Include="RICH.Persistence" Version="1.1.1" />
<PackageReference Include="RICH.Persistence" />
paket add RICH.Persistence --version 1.1.1
#r "nuget: RICH.Persistence, 1.1.1"
#:package RICH.Persistence@1.1.1
#addin nuget:?package=RICH.Persistence&version=1.1.1
#tool nuget:?package=RICH.Persistence&version=1.1.1
RICH.Persistence
A lightweight, asynchronous EF Core persistence abstraction for CQRS applications — built with clean separation between read and write repositories, criteria-based filtering, and built-in pagination support.
📦 Installation
dotnet add package RICH.Persistence
🚀 Quick Start
1. Register in Program.cs
using RICH.Persistence.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(...);
// Register repositories
builder.Services.AddRichPersistence();
2. Inject and use repositories
public class GetUsersHandler
{
private readonly IReadRepository<User, int> _readRepository;
public GetUsersHandler(IReadRepository<User, int> readRepository)
=> _readRepository = readRepository;
public async Task<PagedList<User>> HandleAsync(int page, int size, CancellationToken ct)
{
return await _readRepository.GetListAsync(page, size, tracking: false, ct);
}
}
🧱 Architecture Overview
| Layer | Purpose |
|---|---|
| IReadRepository | Query operations (Get, List, Criteria, Projection, Paging) |
| IWriteRepository | Commands (Create, Update, Delete, Track, SaveChanges) |
| Criteria<T> | Encapsulates query logic (filters, includes, ordering) |
| PagedList<T> | Immutable paged result with total count and metadata |
Repositories are async-only, generic, and CQRS-friendly, designed for clean separation of query and command concerns.
⚙️ Core Features
✅ Asynchronous EF Core queries
✅ Read/write repository separation
✅ Criteria pattern for filtering and includes
✅ Built-in paging with total count and navigation metadata
✅ Strongly-typed projection support
✅ No dependency on UoW or transactions (lightweight)
✅ Fully DI-compatible
🧩 Example Usage
1. Basic paging (no filters or projection)
PagedList<User> users = await _readRepository.GetListAsync(
page: 1,
size: 20,
tracking: false
);
2. Filtered paging (using condition)
PagedList<User> users = await _readRepository.GetListAsync(
page: 1,
size: 10,
condition: u => u.IsActive,
tracking: false
);
3. Criteria-based paging
var criteria = new UserCriteriaBuilder()
.IncludeRoles()
.WhereActive()
.Build();
PagedList<User> users = await _readRepository.GetListAsync(
page: 1,
size: 10,
criteria: criteria
);
4. Projection (to DTOs)
PagedList<UserDto> users = await _readRepository.GetListAsync(
page: 1,
size: 10,
projecter: u => new UserDto { Id = u.Id, Name = u.Name }
);
5. Filtered + Projection paging
PagedList<UserDto> users = await _readRepository.GetListAsync(
page: 2,
size: 5,
condition: u => u.IsActive && u.LastLogin > DateTime.UtcNow.AddDays(-30),
projecter: u => new UserDto
{
Id = u.Id,
Name = u.FirstName + " " + u.LastName,
RoleCount = u.Roles.Count
},
tracking: false
);
6. Criteria + Projection paging
var criteria = new UserCriteriaBuilder()
.WhereActive()
.IncludeRoles()
.Build();
PagedList<UserDto> users = await _readRepository.GetListAsync(
page: 1,
size: 10,
criteria: criteria,
projecter: u => new UserDto
{
Id = u.Id,
Name = u.FirstName + " " + u.LastName,
Roles = u.Roles.Select(r => r.Name).ToList()
},
tracking: false
);
🧬 PagedList<T> Example
{
"list": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
],
"page": 1,
"size": 2,
"totalPages": 5,
"totalCount": 10,
"hasPrevious": false,
"hasNext": true
}
🧩 Criteria Example
1. Basic custom criteria
public class ActiveUsersCriteria : Criteria<User>
{
public override IQueryable<User> Evaluate(IQueryable<User> query)
=> query.Where(u => u.IsActive)
.Include(u => u.Roles);
}
Usage:
var criteria = new ActiveUsersCriteria();
var result = await _readRepository.GetListAsync(page: 1, size: 10, criteria);
2. Domain-specific CriteriaBuilder subclass
public class UserCriteriaBuilder : CriteriaBuilder<User>
{
public UserCriteriaBuilder WhereActive()
{
With(q => q.Where(u => u.IsActive));
return this;
}
public UserCriteriaBuilder IncludeRoles()
{
With(q => q.Include(u => u.Roles));
return this;
}
public UserCriteriaBuilder IncludeDepartment()
{
With(q => q.Include(u => u.Department));
return this;
}
public UserCriteriaBuilder CreatedWithinDays(int days)
{
var since = DateTime.UtcNow.AddDays(-days);
With(q => q.Where(u => u.CreatedAt >= since));
return this;
}
}
Usage:
var criteria = new UserCriteriaBuilder()
.WhereActive()
.IncludeRoles()
.CreatedWithinDays(30)
.Build();
PagedList<User> users = await _readRepository.GetListAsync(1, 10, criteria);
3. Combine criteria dynamically
public class ExtendedUserCriteriaBuilder : UserCriteriaBuilder
{
public ExtendedUserCriteriaBuilder IncludeDepartmentAndRoles()
{
With(new RawCriteria<User>(q => q.Include(u => u.Department)
.Include(u => u.Roles)));
return this;
}
}
Usage:
var criteria = new ExtendedUserCriteriaBuilder()
.WhereActive()
.IncludeDepartmentAndRoles()
.Build();
PagedList<User> users = await _readRepository.GetListAsync(1, 10, criteria);
4. Criteria + projection
var criteria = new UserCriteriaBuilder()
.WhereActive()
.IncludeRoles()
.Build();
PagedList<UserDto> users = await _readRepository.GetListAsync(
page: 1,
size: 10,
criteria: criteria,
projecter: u => new UserDto
{
Id = u.Id,
Name = u.FirstName + " " + u.LastName,
Roles = u.Roles.Select(r => r.Name).ToList()
}
);
5. Multiple builder variants for different use cases
public class InactiveUserCriteriaBuilder : CriteriaBuilder<User>
{
public InactiveUserCriteriaBuilder WithoutRoles()
{
With(q => q.Where(u => !u.IsActive));
return this;
}
}
public class UserWithDepartmentCriteriaBuilder : CriteriaBuilder<User>
{
public UserWithDepartmentCriteriaBuilder IncludeDepartment()
{
With(q => q.Include(u => u.Department));
return this;
}
}
Usage:
var inactiveUsersCriteria = new InactiveUserCriteriaBuilder()
.WithoutRoles()
.Build();
var deptUsersCriteria = new UserWithDepartmentCriteriaBuilder()
.IncludeDepartment()
.Build();
List<User> inactive = await _readRepository.GetListAsync(inactiveUsersCriteria);
List<User> withDept = await _readRepository.GetListAsync(deptUsersCriteria);
6. CriteriaBuilder in CQRS query handler
public record GetRecentActiveUsersQuery(int Page, int Size) : IRequest<PagedList<UserDto>>;
public class GetRecentActiveUsersHandler : IRequestHandler<GetRecentActiveUsersQuery, PagedList<UserDto>>
{
private readonly IReadRepository<User, int> _readRepository;
public GetRecentActiveUsersHandler(IReadRepository<User, int> readRepository)
=> _readRepository = readRepository;
public async Task<PagedList<UserDto>> Handle(GetRecentActiveUsersQuery request, CancellationToken ct)
{
var criteria = new UserCriteriaBuilder()
.WhereActive()
.CreatedWithinDays(7)
.IncludeRoles()
.Build();
return await _readRepository.GetListAsync(
page: request.Page,
size: request.Size,
criteria: criteria,
projecter: u => new UserDto
{
Id = u.Id,
FullName = u.FirstName + " " + u.LastName,
Email = u.Email
},
tracking: false,
ct
);
}
}
Design Philosophy
| Principle | Description |
|---|---|
| Async-first | All database operations are async-only. |
| Lightweight | No UoW, no transactions; direct EF Core usage. |
| Composable | Criteria and expression-based filtering. |
| Extensible | You can override or extend repositories per aggregate. |
| Performant | Uses server-side Select, Skip, and Take in single SQL query. |
📄 License
MIT © 2025 Rəşad Məmmədov
Summary
| Feature | Status |
|---|---|
| Async-only methods | ✅ |
| Criteria support | ✅ |
| Paging | ✅ |
| Projection | ✅ |
| Dependency injection | ✅ |
| NuGet-ready | ✅ |
RICH.Persistence provides a clean, extensible EF Core abstraction layer — ideal for CQRS and Domain-Driven Design applications.
| Product | Versions 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. |
-
net8.0
- Microsoft.EntityFrameworkCore (>= 8.0.20)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.