Skip to content

Integrations

The engine is provider-agnostic and knows only the BCL's value types. Everything else is an extension point, and every package in this section is nothing more than a pre-built use of one:

PackageExtension point it usesPage
Janzen.Pagination.AspNetCoremodel binding, exception filter, OpenAPI transformerASP.NET Core
Janzen.Pagination.PostgreSqlthe LIKE strategy — how a pattern match is emittedPostgreSQL
Janzen.Pagination.NodaTimePaginateTypeSupport — parsing, classification, projectionNodaTime
(your own code)the same registry the NodaTime package usesCustom types

None of them changes what a PaginateConfig<T> looks like. A config written against SQL Server works unchanged on PostgreSQL; only the emitted SQL differs.

Which engines this is measured against

Three legs run in this repository's own suite: PostgreSQL (15.19, 16.15, 17.11 and 18.6), SQLite, and a plain IQueryable over a list. Every claim about those is measured, and the PostgreSQL pages say which server a given one was measured on.

Statements about SQL Server, Oracle, MySQL and Synapse — collation behaviour, the default escape character, [ opening a character range — are read from those engines' own documentation and have not been executed against this library. They are the best information available and are believed correct; they are not evidence of the same kind. Provider legs for them are planned, and this note narrows as they land.

Non-EF IQueryable

Before any of that, there is one adaptation the engine makes on its own. It checks whether the source's provider is Entity Framework Core's own EntityQueryProvider and takes a different path when it is not:

EF providerplain IQueryable (e.g. List<T>.AsQueryable())
pattern matching (search, $ilike, $sw, string $contains)EF.Functions.Like / ILikestring.IndexOf / StartsWith with OrdinalIgnoreCase
$eq and $in on a stringthe column's collation decidesExpression.Equal / Enumerable.Containsordinal, case-sensitive
$lt / $gt / $btw on a stringthe column's collation decidesStringComparison.InvariantCulture
filter valueswrapped in EF.Parameter for plan reuseplain constants
count / materialiseCountAsync / ToListAsyncsynchronous Count / ToList, wrapped in a completed task

So the whole pipeline — filters, search, sort, paging, projection — runs against an in-memory list, which makes unit-testing a PaginateConfig<T> cheap. See Testing your pagination.

Read the first three rows together before relying on the leg for case behaviour: in memory the operators disagree with each other. ?filter.name=$ilike:APPLE matches apple pie while ?filter.name=$eq:APPLE does not, where SQL Server's usual collation matches both and PostgreSQL without the .PostgreSql package matches neither. That is not a defect being reported here — a plain list has no collation to consult, so each operator has to pick something — but it is the reason a case-sensitivity expectation formed against this leg does not survive the move to a database.

There is a third case, and it is refused rather than adapted. A provider that is asynchronous without being Entity Framework Core's — what a queryable-shaped mocking library produces — is neither leg, and the engine answers it with a NotSupportedException that says so — not a PaginateQueryException, because that type is the 400 contract and this is the server's own wiring, so it reaches the client as a 500. Sending it down the in-memory leg would have been the friendlier answer and the wrong one: the two legs disagree on a substantial share of requests, so a test that passed that way would prove nothing about the database. Testing your pagination shows the SQLite in-memory setup to use instead — a real provider, and it costs no more.

Released under the MIT License.