Pafiso.EntityFrameworkCore 2.0.0-beta.1

This is a prerelease version of Pafiso.EntityFrameworkCore.
dotnet add package Pafiso.EntityFrameworkCore --version 2.0.0-beta.1
                    
NuGet\Install-Package Pafiso.EntityFrameworkCore -Version 2.0.0-beta.1
                    
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="Pafiso.EntityFrameworkCore" Version="2.0.0-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Pafiso.EntityFrameworkCore" Version="2.0.0-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="Pafiso.EntityFrameworkCore" />
                    
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 Pafiso.EntityFrameworkCore --version 2.0.0-beta.1
                    
#r "nuget: Pafiso.EntityFrameworkCore, 2.0.0-beta.1"
                    
#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 Pafiso.EntityFrameworkCore@2.0.0-beta.1
                    
#: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=Pafiso.EntityFrameworkCore&version=2.0.0-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Pafiso.EntityFrameworkCore&version=2.0.0-beta.1&prerelease
                    
Install as a Cake Tool

Pafiso

A .NET library for Paging, Filtering, and Sorting with DTO-to-Entity mapping support.

NuGet Version NuGet Downloads Build Status Deploy Status

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; }
}
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:

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 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

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