Repository navigation
EntityFramework Core Extensions
PagedListResult (assembly RzR.ResultMessage.Pagination.EntityFrameworkCore) is the package most consumers actually install. It exposes four extension methods on IQueryable<T> through the static PaginatedQueryExtensions class, plus SOAP/XML adapters for the result envelope.
This page documents each method, its overloads, what part of the request it honors, and when to use which.
| Method | Filter handling | Honours Order/Search/Fields/PredefinedRecord
|
Async |
|---|---|---|---|
GetPaged(page, pageSize) |
None | No (no request object) | No |
GetPaged(request, defaultPrimaryKey?) |
None | Yes | No |
GetPagedAsync(page, pageSize, ct) |
None | No | Yes |
GetPagedAsync(request, defaultPrimaryKey?, ct) |
None | Yes | Yes |
GetPagedWithMainFiltersAsync(request, defaultPrimaryKey?, ct) |
Top-level filters AND-combined; Dependencies ignored |
Yes | Yes |
GetPagedWithFiltersAsync(request, defaultPrimaryKey?, filterLink?, ct) |
Top-level filters combined by filterLink (default And); each filter's Dependencies honored |
Yes | Yes |
All request-bound overloads are generic on TPageRequest : PagedRequest (or PageRequestWithFilters), so you can derive your own request shape and the library will still bind it correctly.
Use these when you do not need search/order/filters and only want to slice an IQueryable<T>:
PagedResult<Product> result = await _db.Products
.OrderBy(p => p.Id)
.GetPagedAsync(page: 1, pageSize: 25, cancellationToken);Both page and pageSize are validated to be positive (ArgumentException otherwise). The execution time is captured via TimeWatchHelper and stored in result.ExecutionDetails.
Use these when the client sends a PagedRequest (or PageRequestWithFilters) but you do not need its Filters collection. Pipeline:
-
Search — apply
request.SearchviaFilterSourceByTextif the search definition is non-null. -
Total count —
query.Count()(orCountAsyncin the async overload). -
Order — apply
request.Order.OrderByPropertyif present, otherwise the[PaginationDefaultOrderProperty]fallback ifOrderByDefaultProperty == true, otherwiseOrderBy(x => 0). -
Page math — compute
PageCount = Math.Ceiling(RowCount / PageSize)andskip = (Page - 1) * PageSize. -
Predefined records — only on page 1 (
skip <= 0). WhendefaultPrimaryKeyis provided and the request has anyPredefinedRecord.PredefinedRecords, the library:- resolves the property to match against (
PredefinedFieldNameif present, otherwise via the attribute / EF model / explicit fallback); - prepends the matched rows to the response;
- takes
pageSize - matchedCountfrom the rest of the query.
- resolves the property to match against (
-
Slice —
Skip(skip).Take(pageSize)(or async equivalent). -
Fields projection — when
request.Fieldsis non-empty, the response items are flattened to the listed property names throughJsonSerializer(handy when shaping stored-procedure outputs). -
Result — set
IsSuccess = trueand writeExecutionDetails.
Important: Filters from PageRequestWithFilters are not applied by these overloads. Use one of the two filter-aware variants below.
return await _db.Products
.AsNoTracking()
.GetPagedAsync(
request,
new DefaultPrimaryKeyDefinition("Id"), // optional
cancellationToken);The simplest filter pipeline. The request must derive from PageRequestWithFilters. The method:
- Validates the request via
IValidatableObject.Validate(...)and throws on failure (ApplicationExceptionwith the first error). - Calls
query.AsSimpleFilterable(request.Filters)to AND-combine the top-level filters (each filter'sDependenciesis ignored). - Delegates to
GetPagedAsync(request, defaultPrimaryKey, ct)to apply search/order/predefined/paging. - Updates
ExecutionDetails.ExecutionTimeMswith the cumulative time.
Use this when the client filters are simple AND-combined predicates (the most common case for back-office grids).
return await _db.Products
.AsNoTracking()
.GetPagedWithMainFiltersAsync(request, cancellationToken: ct);The translated SQL will look like:
WHERE (status = 'active') AND (price > 100) AND (createdOn >= @from)The full filter pipeline. The signature adds filterLink (FilterConditionType.And or FilterConditionType.Or, default And) and honors each filter's Dependencies collection.
return await _db.Products
.AsNoTracking()
.GetPagedWithFiltersAsync(
request,
defaultPrimaryKey: new DefaultPrimaryKeyDefinition("Id"),
filterLink: FilterConditionType.Or,
cancellationToken: ct);The expression looks like (pseudo-SQL):
WHERE
((authorId IN (2)) OR (authorId = 3)) -- one filter + its dependency
AND -- top-level link from filterLink
(createdOn BETWEEN @start AND @end)The combination is built bottom-up:
- For each top-level filter, the per-condition visitor builds a
Predicate<T>. - For each
Dependenceof that filter, a second predicate is built and combined with the parent predicate viaAndAlso/OrElseaccording toParentFilterLinkType. - The resulting per-filter predicates are combined together via
filterLink.
Validation rules from DataFilterValue.Validate(...) are enforced first; an invalid request throws ApplicationException carrying the first error. The Web layer wraps this into a 400 ProblemDetails response.
The package ships two ToSoapXmlPagedResult(...) extensions that flatten a PagedResult<T> (or IPagedResult<T>) into a SoapXmlPagedResult carrying string messages and execution details suitable for XmlSerializer. These are used by BaseApiPagedResultController.PagedXmlResult<T> to produce a text/xml response when an XML payload is required.
SoapXmlPagedResult xml = result.ToSoapXmlPagedResult();
string serialized = ObjectExtensions.SerializeToString(xml);The serialization is symmetrical: a paged result that round-trips through SOAP/XML retains all paging metadata, the response items, the success flag and the messages.
Every async method accepts CancellationToken as the last parameter (defaults to CancellationToken.None). The token is forwarded to ToListAsync / CountAsync. If the token fires after the count but before the slice, the partial work is discarded and OperationCanceledException is propagated as expected.
- The library issues two database trips for any non-empty request: one for
Count()and one forSkip(skip).Take(pageSize). With predefined records the slice can be split in two queries, capped atpageSize - matchedCountrows for the main query. - All reflection lookups are cached statically.
- The default-primary-key probe walks
DbContext.Modelonce per query type; if you need to bypass EF Core entirely (DTO projections without an entity counterpart), setDefaultPrimaryKeyDefinition.DefaultPrimaryKeyexplicitly. -
GetPaged*returnsPagedResult<TSource>, notIQueryable<TSource>. Materialization happens inside the extension; do not callToListAsync()again on the result.
For end-to-end controller and minimal-API examples that combine these extensions with the Web integration, see Recipes.
PagedListResult is maintained by RzR. GitHub repository.
Start here
Reference
- Data Models Reference
- Expression Builders (Common)
- EntityFramework Core Extensions
- Query String Syntax
ASP.NET Core integration
- Web Integration Overview
- MVC Integration
- Minimal API Integration
- Configuration and Options
- Allow List Registry
- Response Headers and ProblemDetails
- OpenAPI and ApiExplorer
Operations