ToolBox.EntityFramework
2.4.0
dotnet add package ToolBox.EntityFramework --version 2.4.0
NuGet\Install-Package ToolBox.EntityFramework -Version 2.4.0
<PackageReference Include="ToolBox.EntityFramework" Version="2.4.0" />
<PackageVersion Include="ToolBox.EntityFramework" Version="2.4.0" />
<PackageReference Include="ToolBox.EntityFramework" />
paket add ToolBox.EntityFramework --version 2.4.0
#r "nuget: ToolBox.EntityFramework, 2.4.0"
#:package ToolBox.EntityFramework@2.4.0
#addin nuget:?package=ToolBox.EntityFramework&version=2.4.0
#tool nuget:?package=ToolBox.EntityFramework&version=2.4.0
ToolBox.EntityFramework.Filters
A lightweight, high-performance library for dynamic filtering, sorting, and pagination with Entity Framework Core.
Features
โจ Dynamic Filtering - Build complex filter expressions with AND/OR logic
๐ Multi-Column Sorting - Sort by multiple properties with direction control
๐ Smart Pagination - Efficient server-side pagination with rich metadata
โก Performance Optimized - Reflection caching and AsNoTracking() support
๐ฏ FluentValidation Ready - Built-in validators included
๐งช Type-Safe - Expression trees that translate directly to SQL
๐ง ASP.NET Core Integration - Extension methods and DI registration
Quick Start
Basic Pagination
var request = new PageRequest(pageNumber: 1, pageSize: 20);
var result = await dbContext.Products.ToPageAsync(request);
Console.WriteLine($"Page {result.PageNumber}/{result.TotalPages}");
Console.WriteLine($"Total Items: {result.TotalItems}");
With Sorting
var request = new PageRequest(1, 20)
{
Sorts = new[]
{
new SortDescriptor { PropertyName = "Name", Direction = SortDirection.Ascending },
new SortDescriptor { PropertyName = "CreatedDate", Direction = SortDirection.Descending }
}
};
var result = await dbContext.Products.ToPageAsync(request);
With Filtering
var request = new PageRequest(1, 20)
{
Filters = new[]
{
new FilterGroup
{
Filters = new[]
{
new ExpressionFilter
{
PropertyName = "Price",
ValueString = "50",
Comparison = Comparison.GreaterThan
},
new ExpressionFilter
{
PropertyName = "Category",
ValueString = "Electronics",
Comparison = Comparison.Equal
}
},
FilterAssociation = FilterAssociation.And
}
}
};
var result = await dbContext.Products.ToPageAsync(request);
Complete Example
var request = new PageRequest(1, 20)
{
Filters = new[]
{
new FilterGroup
{
Filters = new[]
{
new ExpressionFilter { PropertyName = "Price", ValueString = "100", Comparison = Comparison.GreaterThan },
new ExpressionFilter { PropertyName = "InStock", ValueString = "true", Comparison = Comparison.Equal }
},
FilterAssociation = FilterAssociation.And
}
},
Sorts = new[]
{
new SortDescriptor { PropertyName = "Name", Direction = SortDirection.Ascending }
}
};
var result = await dbContext.Products.ToPageAsync(request);
Supported Comparisons
| Comparison | Description | Supported Types |
|---|---|---|
Equal |
Exact match | All types |
NotEqual |
Not equal | All types |
GreaterThan |
Greater than | Numeric, DateTime |
GreaterThanOrEqual |
Greater than or equal | Numeric, DateTime |
LessThan |
Less than | Numeric, DateTime |
LessThanOrEqual |
Less than or equal | Numeric, DateTime |
Contains |
Contains substring | String only |
StartsWith |
Starts with | String only |
EndsWith |
Ends with | String only |
ASP.NET Core Integration
Program.cs
using ToolBox.EntityFramework.Filters;
var builder = WebApplication.CreateBuilder(args);
// Register validators and services
builder.Services.AddToolBoxFilters();
var app = builder.Build();
Controller Usage
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly AppDbContext _context;
public ProductsController(AppDbContext context)
{
_context = context;
}
[HttpPost("search")]
public async Task<ActionResult<PageOf<Product>>> Search(
[FromBody] PageRequest request,
CancellationToken cancellationToken)
{
// Automatic validation with FluentValidation
var result = await _context.Products
.ToPageAsync(request, cancellationToken);
return Ok(result);
}
}
API Request Example
POST /api/products/search
{
"pageNumber": 1,
"pageSize": 20,
"filters": [
{
"filters": [
{
"propertyName": "Price",
"valueString": "50",
"comparison": 1,
"filterAssociation": 0
},
{
"propertyName": "Category",
"valueString": "Electronics",
"comparison": 0,
"filterAssociation": 0
}
],
"filterAssociation": 0
}
],
"sorts": [
{
"propertyName": "Name",
"direction": 0
}
]
}
API Response Structure
{
"pageNumber": 1,
"pageSize": 20,
"totalItems": 156,
"totalPages": 8,
"hasPreviousPage": false,
"hasNextPage": true,
"items": [
{
"id": 1,
"name": "Product A",
"price": 99.99,
"category": "Electronics"
}
]
}
Advanced Features
Complex Filter Logic
Combine multiple filter groups with AND/OR logic:
var filters = new[]
{
new FilterGroup
{
// (Price > 100 AND Category = "Electronics")
Filters = new[]
{
new ExpressionFilter { PropertyName = "Price", ValueString = "100", Comparison = Comparison.GreaterThan },
new ExpressionFilter { PropertyName = "Category", ValueString = "Electronics", Comparison = Comparison.Equal }
},
FilterAssociation = FilterAssociation.And
},
new FilterGroup
{
// OR (FeaturedProduct = true)
Filters = new[]
{
new ExpressionFilter { PropertyName = "FeaturedProduct", ValueString = "true", Comparison = Comparison.Equal }
},
FilterAssociation = FilterAssociation.Or
}
};
SQL Output:
WHERE (Price > 100 AND Category = 'Electronics') OR (FeaturedProduct = 1)
ORDER BY Name ASC
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
Case-Insensitive Property Names
Property names are matched case-insensitively:
// All of these work
new SortDescriptor { PropertyName = "Name" }
new SortDescriptor { PropertyName = "name" }
new SortDescriptor { PropertyName = "NAME" }
Validation
Built-in FluentValidation validators ensure:
- Page number โฅ 1
- Page size between 1-100 (configurable)
- Valid property names
- Non-empty filter values
- Valid comparison operators
Performance
- Reflection Caching: PropertyInfo lookups cached in
ConcurrentDictionary - Single Query: Filters and sorts translated to single SQL query
- No Tracking:
AsNoTracking()applied automatically - Expression Trees: Direct translation to SQL (no in-memory filtering)
Configuration
Custom Page Size Limits
public class PageRequest
{
private const int MaxPageSize = 100; // Modify this constant
// ...
}
Clear Reflection Cache
// Useful for testing or hot reload scenarios
SortBuilder.ClearCache();
ExpressionBuilder.ClearCache();
API Reference
PageRequest
| Property | Type | Default | Description |
|---|---|---|---|
PageNumber |
int |
1 |
Current page (1-based) |
PageSize |
int |
10 |
Items per page (max: 100) |
Filters |
IEnumerable<FilterGroup> |
null |
Filter groups |
Sorts |
IEnumerable<SortDescriptor> |
null |
Sort descriptors |
PageOf<T>
| Property | Type | Description |
|---|---|---|
PageNumber |
int |
Current page number |
PageSize |
int |
Items per page |
TotalItems |
int |
Total items across all pages |
TotalPages |
int |
Total number of pages |
HasPreviousPage |
bool |
True if previous page exists |
HasNextPage |
bool |
True if next page exists |
Items |
ICollection<T> |
Items in current page |
Extension Methods
// IQueryable<T> extensions
Task<PageOf<T>> ToPageAsync(PageRequest request, CancellationToken cancellationToken = default)
Task<PageOf<T>> ToPageAsync(int pageNumber, int pageSize, ...)
Requirements
- .NET 10.0+
- Entity Framework Core 9.0+
- FluentValidation 11.0+ (optional, for validation)
Error Handling
The library provides detailed error messages:
// Property not found
Property 'InvalidName' not found on type 'Product'. Available properties: Id, Name, Price, Category
// Invalid type for string operation
Contains comparison is only supported for string properties. Property 'Price' is of type 'Decimal'.
// Empty property name
Sort property name cannot be empty.
Best Practices
- Use
AsNoTracking()- Already applied automatically for read queries - Index filtered/sorted columns - Add database indexes on frequently queried properties
- Limit page size - Default max is 100 items per page
- Validate input - Use provided FluentValidation validators
- Cache property lookups - Handled automatically via
ConcurrentDictionary
Performance Tips
// โ
Good - Single query
var result = await dbContext.Products
.Include(p => p.Category)
.ToPageAsync(request);
// โ Bad - Multiple queries
var products = await dbContext.Products.ToListAsync(); // Loads everything
var filtered = products.Where(...); // In-memory filtering
| 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
- FluentValidation (>= 12.1.1)
- Microsoft.EntityFrameworkCore (>= 10.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.