OrionPage.EntityFrameworkCore 0.1.0

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

<p align="center"> <img src="docs/logo.png" alt="OrionPage" width="150" /> </p>

OrionPage

CI/CD NuGet

Pagination that stays fast on page 10,000. Keyset (cursor) paging for EF Core with an opaque cursor and a one-line query extension — because OFFSET 100000 is a table scan, and every list endpoint eventually pays for it.

Almost every list endpoint ships .Skip(page * size).Take(size). It works in the demo and rots in production: OFFSET 100000 LIMIT 20 makes the database read and discard 100,000 rows every time (deep pages get linearly slower), and inserting a row mid-scan shifts everything so clients skip or duplicate records. The correct fix — keyset paging (WHERE (created, id) < (@c, @i) ORDER BY created DESC, id DESC LIMIT 20) — is constant-time and stable, but composing the tuple predicate, encoding a multi-column cursor, and handling ties is fiddly enough that most teams don't, or do it wrong. OrionPage owns that mechanism so nobody hand-writes tuple-comparison predicates again.

Packages

  • OrionPage — the framework-free core: Page<T> and the opaque Cursor codec. Reflection-free and AOT-clean.
  • OrionPage.EntityFrameworkCore — the ToKeysetPageAsync query extension plus the keyset engine (KeysetPredicateBuilder, KeysetSortKey): reads your OrderBy chain, builds the tuple-comparison predicate, and executes. (Not AOT-published — the predicate builder relies on operator-method reflection that NativeAOT trims, and EF Core is not AOT-clean.)

Install

dotnet add package OrionPage.EntityFrameworkCore

Quick start

Order the query (with OrderBy/ThenBy as the final operators, ending in a unique tie-breaker like Id), then page it:

using Moongazing.OrionPage;
using Moongazing.OrionPage.EntityFrameworkCore;

public Task<Page<Order>> ListAsync(string? cursor, CancellationToken ct) =>
    db.Orders
      .OrderByDescending(o => o.CreatedUtc).ThenByDescending(o => o.Id) // stable total ordering
      .ToKeysetPageAsync(cursor, pageSize: 20, ct);

Page<T> carries the rows, an opaque forward NextCursor, and HasMore:

{ "items": [ /* ... */ ], "nextCursor": "eyJjIjoiMjAyNi0wNy0...", "hasMore": true }

Fetch the next page by feeding NextCursor back in. When HasMore is false, NextCursor is null.

Projecting to a DTO

Order and page the entity, then map the returned items — the always-translatable shape:

var page = await db.Orders
    .OrderByDescending(o => o.CreatedUtc).ThenByDescending(o => o.Id)
    .ToKeysetPageAsync(cursor, 20, ct);

var dtos = page.Items.Select(o => new OrderDto(o.Id, o.CreatedUtc, o.Total)).ToList();

You can also project before ordering when EF can translate the ordering — an anonymous-type projection ordered by its members works; ordering through a positional-record constructor does not.

What it guarantees

  • Constant-time deep pages. A continuation is a WHERE tuple-comparison seek against your sort index — never OFFSET. Page 10,000 costs the same as page 1.
  • No skipped or duplicated rows across concurrent writes, provided the ordering is a stable total order — which is why the last sort key must be a unique tie-breaker (e.g. Id). Ties on earlier keys are handled correctly.
  • Opaque cursors. The cursor is a compact, URL-safe, reflection-free binary token; a malformed or tampered cursor is a typed InvalidCursorException (mapped to 400 by the Wave 3 web binding), not a 500. HMAC signing lands in a later wave.
  • No unbounded COUNT(*). HasMore answers "is there a next page?" without the O(n) cost of a total count.

Observability

A Moongazing.OrionPage activity source emits an OrionPage.keyset span per page with orion.page.size and orion.page.has_more attributes, on the family's OrionInstrumentation spine.

Roadmap

This is the Wave 1 keyset engine (v0.1): keyset paging over EF Core, an opaque cursor, and multi-column ordering read from the OrderBy chain, AOT-clean. Later waves add HMAC-signed cursors and nullable/multi-directional sort-key hardening (W2), the minimal-API [AsCursor] binding + .WithKeysetPaging() filter + OpenAPI + OrionEnvelope meta.page (W3, GA), and bidirectional cursors + an offset-compat shim + a Dapper adapter (W4). See CHANGELOG.md.

Supported sort-key types in this release are the integer, floating-point, decimal, and date/time types; string, Guid, and nullable keys are hardened in Wave 2. OrionPage does not do "jump to page N" (keyset is sequential by nature — that is the anti-pattern being retired), does not compute a total count by default, and paginates whatever IQueryable it is given (compose authorization filters upstream).

Versioning

Follows Semantic Versioning. Multi-targets net8.0, net9.0, and net10.0. Binds to Orion.Abstractions 1.x; the EF Core extension requires EF Core 8+. The framework-free core is AOT- and trim-clean (verified by a native-binary smoke test in CI); the EF Core extension is not NativeAOT-published because EF Core itself is not AOT-clean.

Documentation

Contributing

Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.

More from the Orion family

Focused .NET libraries built to one quality bar. Each is usable on its own; several share the small Orion.Abstractions contracts spine, but there is no deep dependency web — pick only what you need:

  • Orion.Abstractions — the shared contracts spine: telemetry, options, result, clock
  • OrionClock — a TimeProvider-based clock with TTL / deadline vocabulary
  • OrionResult — Result/Option types and a shared error vocabulary
  • OrionResilience — retry, backoff, and timeout on OrionClock
  • OrionRate — rate limiting (token-bucket / sliding-window) on OrionClock
  • OrionGuard — validation, guard clauses, DDD primitives, domain events
  • OrionAudit — automatic EF Core change-audit trail
  • OrionBeacon — leader election with fencing tokens
  • OrionGrant — permission / authorization checks
  • OrionInbox — transactional inbox for exactly-once effects
  • OrionKey — source-generated strongly-typed IDs
  • OrionLedger — API-key issuance, verification, and rotation
  • OrionLens — ambient correlation-context propagation
  • OrionLock — distributed locks with fencing tokens
  • OrionOnce — idempotency keys for exactly-once request handling
  • OrionPatch — transactional outbox for EF Core
  • OrionRelay — outbound webhook delivery (HMAC, retries, backoff)
  • OrionSaga — sagas / process managers for long-running workflows
  • OrionShade — sensitive-data redaction for logs and telemetry
  • OrionStream — server-sent events / streaming hub
  • OrionVault — field-level encryption for EF Core

See it all working together in OrionShowcase, a production-shaped banking sample.

License

MIT.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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
0.1.0 119 8/9/2026