Pafiso.EntityFrameworkCore
2.0.0-beta.1
dotnet add package Pafiso.EntityFrameworkCore --version 2.0.0-beta.1
NuGet\Install-Package Pafiso.EntityFrameworkCore -Version 2.0.0-beta.1
<PackageReference Include="Pafiso.EntityFrameworkCore" Version="2.0.0-beta.1" />
<PackageVersion Include="Pafiso.EntityFrameworkCore" Version="2.0.0-beta.1" />
<PackageReference Include="Pafiso.EntityFrameworkCore" />
paket add Pafiso.EntityFrameworkCore --version 2.0.0-beta.1
#r "nuget: Pafiso.EntityFrameworkCore, 2.0.0-beta.1"
#:package Pafiso.EntityFrameworkCore@2.0.0-beta.1
#addin nuget:?package=Pafiso.EntityFrameworkCore&version=2.0.0-beta.1&prerelease
#tool nuget:?package=Pafiso.EntityFrameworkCore&version=2.0.0-beta.1&prerelease
Pafiso
A .NET library for Paging, Filtering, and Sorting with DTO-to-Entity mapping support.
Pafiso enables building dynamic, type-safe queries from query string parameters by mapping between DTOs (mapping models) and entity classes. Perfect for building flexible REST APIs with filtering, sorting, and pagination.
Features
- Fluent API - Clean, discoverable syntax with IntelliSense support
- Type-Safe - Strong typing with DTO-to-Entity mapping
- Entity Framework Core - Full async support with optimized SQL via
EF.Functions.Like - Flexible - Multiple API styles to fit your use case
- Auto-Mapping - 1:1 field mapping when names match
- Customizable - Transform values, map nested properties, plug in custom expression builders
Installation
Install Pafiso via NuGet Package Manager:
PM> Install-Package Pafiso.AspNetCore
PM> Install-Package Pafiso.EntityFrameworkCore # For EF Core async support
Or via the .NET CLI:
dotnet add package Pafiso.AspNetCore
dotnet add package Pafiso.EntityFrameworkCore # For EF Core async support
Quick Start
1. Define Your DTO and Entity
using Pafiso;
// DTO - Represents incoming query parameters
public class ProductFilterDto : MappingModel {
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Category { get; set; }
}
// Entity - Your database model
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
2. Use the Fluent API (Recommended)
using Pafiso.EntityFrameworkCore;
[HttpGet]
public async Task<PagedList<Product>> GetProducts() {
return await _dbContext.Products
.WithPafiso(Request.Query, configure: opt => {
opt.WithPaging();
opt.WithFiltering<ProductFilterDto>()
.Map(dto => dto.ProductId, entity => entity.Id)
.Map(dto => dto.ProductName, entity => entity.Name);
// Category maps 1:1 automatically
opt.WithSorting<ProductFilterDto>();
})
.ToPagedListAsync();
}
That's it! The query string is automatically parsed and applied.
Query String Example
GET /api/products?skip=0&take=10
&filters[0][fields]=Category&filters[0][op]=eq&filters[0][val]=Electronics
&sortings[0][prop]=ProductName&sortings[0][ord]=asc
Response:
{
"totalEntries": 150,
"pageNumber": 0,
"pageSize": 10,
"entries": [...]
}
API Styles
Pafiso offers three API styles to fit different use cases:
Style 1: Fluent Builder (Recommended)
Perfect for simple, one-time queries:
using Pafiso.EntityFrameworkCore;
var products = await _dbContext.Products
.WithPafiso(Request.Query, configure: opt => {
opt.WithPaging();
opt.WithFiltering<ProductFilterDto>();
})
.ToPagedListAsync();
Style 2: SearchParameters (For Reusability)
Build once, reuse multiple times:
using Pafiso.AspNetCore;
using Pafiso.EntityFrameworkCore;
// Build SearchParameters
var searchParams = Request.Query.ToSearchParameters<Product>(builder => {
builder.WithPaging();
builder.WithFiltering<ProductFilterDto>()
.Map(dto => dto.ProductId, entity => entity.Id);
});
// Reuse across different queries
var activeProducts = await _dbContext.Products
.Where(p => p.IsActive)
.WithPafiso(searchParams)
.ToPagedListAsync();
var featuredProducts = await _dbContext.Products
.Where(p => p.IsFeatured)
.WithPafiso(searchParams)
.ToPagedListAsync();
Style 3: Manual with Mapper (Legacy)
For maximum control:
var mapper = new FieldMapper<ProductFilterDto, Product>(settings)
.Map(dto => dto.ProductId, entity => entity.Id);
var searchParams = Request.Query.ToSearchParameters<ProductFilterDto, Product>(mapper);
var (countQuery, pagedQuery) = searchParams.ApplyToIQueryable(_dbContext.Products);
var totalCount = await countQuery.CountAsync();
var items = await pagedQuery.ToListAsync();
Core Concepts
Automatic 1:1 Mapping
When DTO and Entity property names match, no explicit mapping is needed:
public class ProductFilterDto : MappingModel {
public string Category { get; set; } // Matches Product.Category
}
// Category maps automatically
opt.WithFiltering<ProductFilterDto>();
Custom Field Mapping
Map fields with different names:
opt.WithFiltering<ProductFilterDto>()
.Map(dto => dto.ProductId, entity => entity.Id)
.Map(dto => dto.ProductName, entity => entity.Name);
Value Transformation
Transform values before filtering:
opt.WithFiltering<ProductFilterDto>()
.MapWithTransform(
dto => dto.PriceInDollars,
entity => entity.PriceInCents,
value => decimal.Parse(value ?? "0") * 100
);
Optional Components
All features are opt-in:
.WithPafiso(Request.Query, configure: opt => {
opt.WithPaging(); // Optional
opt.WithFiltering<Dto>(); // Optional
opt.WithSorting<Dto>(); // Optional
});
Supported Operators
Filter Operators
| Operator | Description | Example |
|---|---|---|
eq |
Equals | filters[0][op]=eq&filters[0][val]=Electronics |
neq |
Not equals | filters[0][op]=neq&filters[0][val]=Books |
gt |
Greater than | filters[0][op]=gt&filters[0][val]=100 |
gte |
Greater than or equal | filters[0][op]=gte&filters[0][val]=100 |
lt |
Less than | filters[0][op]=lt&filters[0][val]=50 |
lte |
Less than or equal | filters[0][op]=lte&filters[0][val]=50 |
contains |
String contains | filters[0][op]=contains&filters[0][val]=laptop |
startswith |
String starts with | filters[0][op]=startswith&filters[0][val]=Pro |
endswith |
String ends with | filters[0][op]=endswith&filters[0][val]=Max |
Sort Orders
| Order | Description | Example |
|---|---|---|
asc |
Ascending | sortings[0][ord]=asc |
desc |
Descending | sortings[0][ord]=desc |
Advanced Usage
Custom Settings
Configure string comparison and other behaviors:
var settings = new PafisoSettings {
StringComparison = StringComparison.OrdinalIgnoreCase
};
.WithPafiso(Request.Query, settings, configure: opt => {
opt.WithFiltering<ProductFilterDto>();
});
Nested Properties
Map to nested entity properties:
public class Product {
public Category Category { get; set; }
}
public class Category {
public string Name { get; set; }
}
opt.WithFiltering<ProductFilterDto>()
.Map(dto => dto.CategoryName, entity => entity.Category.Name);
Multiple Filters (AND Logic)
Multiple filters are combined with AND:
?filters[0][fields]=Category&filters[0][op]=eq&filters[0][val]=Electronics
&filters[1][fields]=ProductId&filters[1][op]=gt&filters[1][val]=100
Result: Category = 'Electronics' AND ProductId > 100
Multiple Fields (OR Logic)
Single filter with multiple fields uses OR:
?filters[0][fields]=Name,Description&filters[0][op]=contains&filters[0][val]=laptop
Result: Name LIKE '%laptop%' OR Description LIKE '%laptop%'
Case Sensitivity
Filters default to case-insensitive. Override per-filter via query string:
?filters[0][fields]=Name&filters[0][op]=eq&filters[0][val]=Test&filters[0][case]=true
Repository Pattern
public interface IProductRepository {
Task<PagedList<Product>> GetProductsAsync(SearchParameters searchParams);
}
public class ProductRepository : IProductRepository {
private readonly AppDbContext _context;
public async Task<PagedList<Product>> GetProductsAsync(SearchParameters searchParams) {
return await _context.Products
.WithPafiso(searchParams)
.ToPagedListAsync();
}
}
Entity Framework Core Optimization
For optimized SQL generation with EF Core, use WithEfFiltering<TMapping>() instead of WithFiltering<TMapping>(). This automatically uses EF.Functions.Like for case-insensitive string operations:
using Pafiso.EntityFrameworkCore;
var products = await _dbContext.Products
.WithPafiso(Request.Query, configure: opt => {
opt.WithPaging();
opt.WithEfFiltering<ProductFilterDto>()
.Map(dto => dto.ProductId, entity => entity.Id);
opt.WithSorting<ProductFilterDto>();
})
.ToPagedListAsync();
You can also configure case sensitivity defaults:
opt.WithEfFiltering<ProductFilterDto>(new EfFilteringSettings {
CaseSensitive = true // Default to case-sensitive matching
});
To control EF Core LIKE usage, pass EfCoreSettings to WithPafiso:
var settings = new EfCoreSettings { UseEfCoreLikeForCaseInsensitive = false };
await _dbContext.Products.WithPafiso(Request.Query, settings, opt => { /* ... */ }).ToPagedListAsync();
DI Registration
Register Pafiso services for dependency injection:
using Pafiso.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Register Pafiso with auto-detection of JSON settings
builder.Services.AddPafiso();
// Or configure manually
builder.Services.AddPafiso(settings => {
settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
// Register field mappers (for legacy/manual API style)
builder.Services.AddFieldMapper<ProductFilterDto, Product>(mapper => {
mapper.Map(dto => dto.ProductId, entity => entity.Id);
mapper.Map(dto => dto.ProductName, entity => entity.Name);
});
Why Pafiso?
Before Pafiso
[HttpGet]
public async Task<IActionResult> GetProducts(
[FromQuery] string? category,
[FromQuery] decimal? minPrice,
[FromQuery] int page = 0,
[FromQuery] int pageSize = 10,
[FromQuery] string? sortBy = "name",
[FromQuery] string? sortOrder = "asc") {
var query = _dbContext.Products.AsQueryable();
if (!string.IsNullOrEmpty(category))
query = query.Where(p => p.Category == category);
if (minPrice.HasValue)
query = query.Where(p => p.Price >= minPrice.Value);
query = sortBy?.ToLower() switch {
"name" => sortOrder == "desc"
? query.OrderByDescending(p => p.Name)
: query.OrderBy(p => p.Name),
"price" => sortOrder == "desc"
? query.OrderByDescending(p => p.Price)
: query.OrderBy(p => p.Price),
_ => query.OrderBy(p => p.Name)
};
var total = await query.CountAsync();
var items = await query.Skip(page * pageSize).Take(pageSize).ToListAsync();
return Ok(new { total, items });
}
After Pafiso
[HttpGet]
public async Task<PagedList<Product>> GetProducts() {
return await _dbContext.Products
.WithPafiso(Request.Query, configure: opt => {
opt.WithPaging();
opt.WithFiltering<ProductFilterDto>();
opt.WithSorting<ProductFilterDto>();
})
.ToPagedListAsync();
}
Benefits:
- 90% less boilerplate code
- Type-safe with compile-time checking
- Flexible query strings without code changes
- Automatic parameter validation
- Consistent API across endpoints
- Testable and maintainable
Performance
Pafiso generates optimized LINQ expressions that translate to efficient SQL:
-- Generated SQL (with EF Core)
SELECT COUNT(*) FROM Products WHERE Category = 'Electronics';
SELECT * FROM Products
WHERE Category = 'Electronics'
ORDER BY Name ASC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;
No reflection in hot paths, no dynamic SQL, just clean LINQ-to-SQL.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
| 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
- Microsoft.EntityFrameworkCore (>= 10.0.2)
- Pafiso (>= 2.0.0-beta.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0-beta.1 | 43 | 2/13/2026 |
| 1.11.0 | 95 | 1/23/2026 |