Nextorm is a high-performance, zero-SQL object-relational mapper for .NET. It generates typed SQL from LINQ-like expressions and maps rows back to classes, interfaces, anonymous types or tuples — with no change tracking and no mandatory entity class.
Documentation: alexeyshirshov.github.io/nextorm
- Query compilation and parameterization — SQL is built once and cached; values are bound as parameters.
- Two ways to reuse a query — the implicit plan cache and the explicit
Prepare(). - Entities are optional — query tables and columns directly with
TableAlias, or map with attributes / fluent API. - Rich projection targets — classes, interfaces, records, anonymous types, tuples, scalars and nested projections.
- No change tracking — lean, allocation-conscious materialization built for read-heavy data access.
- Six database engines — SQLite, SQL Server, PostgreSQL, MySQL, MariaDB, ClickHouse, plus a built-in in-memory provider.
- Native extreme-row selection —
SelectWhereMax/SelectWhereMinautomatically use PostgreSQLDISTINCT ON/LIMIT 1and ClickHouseargMin/argMax, with a portable window-function fallback elsewhere. - Extensible without forking — public query components (
IQueryComponent+WithComponent), global query filters, value converters, SQL functions, interceptors, custom dialects and renderers. NoInternalsVisibleTo, no translator registry and no global mutable state. - Zero-config NativeAOT —
PublishAot=trueis the only change: no compiled model, no precompilation step, no interceptor opt-in and no manual entity/query registration. Partial coverage today (SQLite, .NET 11 /linux-x64); see the NativeAOT guide.
dotnet add package nextorm
dotnet add package nextorm.sqlite # pick one provider1.0.9 is the first stable release, so --prerelease is not needed; previews (1.0.10-a.x and later)
still require it. Package Manager Console equivalent: Install-Package nextorm.
| Package | Provider | Driver |
|---|---|---|
nextorm |
Core engine + in-memory provider | — |
nextorm.sqlite |
SQLite | Microsoft.Data.Sqlite |
nextorm.sqlserver |
SQL Server | Microsoft.Data.SqlClient |
nextorm.postgres |
PostgreSQL | Npgsql |
nextorm.mysql |
MySQL | MySqlConnector |
nextorm.mariadb |
MariaDB | MySqlConnector |
nextorm.clickhouse |
ClickHouse | ClickHouse.Driver |
nextorm.extensions |
Query components: SelectWhereMin/SelectWhereMax, SelectGroup/SelectGroupHaving |
— |
nextorm.entityframeworkcore |
EF Core integration: db.CreateNextOrmContext() over an existing DbContext |
Microsoft.EntityFrameworkCore.Relational |
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.Data.Sqlite;
using NextORM.Core;
using NextORM.Sqlite;
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var builder = new DataContextBuilder().UseSqlite(connection);
using var dataContext = builder.CreateDataContext();
await foreach (var row in dataContext.From<ISimpleEntity>()
.Select(entity => new { entity.Id })
.ToAsyncEnumerable())
{
Console.WriteLine($"Id = {row.Id}");
}
[SqlTable("simple_entity")]
public interface ISimpleEntity
{
[Key]
[Column("id")]
int Id { get; set; }
}The generated SQL is a plain select id from simple_entity. Continue with the
Quickstart guide.
Avoid rebuilding a query plan on every execution in two independent ways: the implicit plan cache
(used automatically by the terminal methods) and the explicit
Prepare returning an
IPreparedQueryCommand<TResult>.
They differ in cost, lifetime and thread-safety rules — see
Query reuse: cache vs Prepare.
Extensibility is a first-class part of the design, not a set of internal escape hatches. Every hook
is public and usable from a separate assembly — there is no InternalsVisibleTo, no translator
registry and no global mutable state.
-
Query components (structural operators). Implement
IQueryComponentand attach it withWithComponent. A component validates itself inPrepare, contributes SQL structure inBuildQueryand carries a descriptor that participates in the plan key, so a warm plan-cache hit does not rebuild it. Thenextorm.extensionspackage is built entirely on this surface:using NextORM.Extensions; var leaders = dataContext.From<Player>() .SelectWhereMax(p => p.Score, groupBy: p => p.TeamId) .Select(p => new { p.Id, p.TeamId, p.Score });
SelectWhereMax/SelectWhereMinandSelectGroup/SelectGroupHavingare ordinary components — your own package can add operators the same way, with the same plan cache and parameter binding. -
Configuration hooks — global query filters (tenant isolation, soft delete), value converters, naming conventions,
[SqlFunction]declarations and query/connection interceptors. -
Custom dialects and renderers — subclass a provider context and its dialect (and, on PostgreSQL and ClickHouse, the native extreme-row renderer) when the emitted SQL itself must change.
The complete guide — which hook to choose, who calls it and when, and a working example for each — is Extensibility.
Partial and experimental: published native binaries run with the SQLite provider on net11.0
(linux-x64), with JIT-parity checks for interface entities, captured predicates, anonymous
projections, nullable scalars, SQLite JSON functions and grouped aggregates. Every push publishes
the native runner and executes it in CI. The remaining providers, the EF Core bridge and the
residual AOT/trim diagnostics are tracked in
#370; see the
NativeAOT guide.
That is the lightest AOT migration contract among the compared libraries. EF Core needs a compiled model
plus interceptor-based query precompilation and rejects dynamically composed queries; Dapper needs
Dapper.AOT with [module: DapperAot], an InterceptorsNamespaces opt-in and statically known SQL;
linq2db does not officially support NativeAOT. On a 13 GB SQLite reporting app nextorm published natively
with no source changes — 8.2 MB binary, 0.12 s, 30 MB RSS — against EF Core's 17.3 MB, 0.23 s and 42 MB
after two code-generation workarounds (Dapper.AOT reaches 3.5 MB and 0.12 s only with hand-written SQL).
The reference app ships in the repository:
examples/nextorm.examples.oc-report.
Benchmark conditions: provider SQLite (Microsoft.Data.Sqlite); async queries; each cell is the fastest
method for that ORM in the linked report. Machine: AMD EPYC 7763 (4 logical / 2 physical cores), .NET 11.0.0,
BenchmarkDotNet 0.15.8 (ShortRun + InProcessEmitToolchain). As of 2026-11-11. The last twelve rows are
the basic-feature scenarios (Category A in the comparison
article): Nextorm through Prepare()
against the compiled form of EF Core and linq2db and raw Dapper, ten queries per iteration, so each cell there
is the per-query mean. Those rows come from the Category A suite run on 2026-11-11.
| Method | Nextorm | Dapper | linq2db | EF Core |
|---|---|---|---|---|
| Data fetch | 10.75 μs | 15.80 μs | 16.50 μs | 38.46 μs |
| Wide data fetch | 9.135 ms | 9.883 ms | 10.594 ms | 10.998 ms |
| Where | 915.0 μs | 1,378.8 μs | 1,432.3 μs | 3,540.1 μs |
| Simulate work | 8.610 ms | 27.766 ms | 418.616 ms | 109.613 ms |
| Any | 923.5 μs | 1,421.9 μs | 1,523.8 μs | 3,742.6 μs |
| FirstOrDefault | 93.64 μs | 148.96 μs | 363.31 μs | 368.44 μs |
| SingleOrDefault | 92.38 μs | 142.69 μs | 372.78 μs | 369.31 μs |
CASE WHEN |
9.45 μs | 14.64 μs | 15.43 μs | 36.91 μs |
| CTE | 9.89 μs | 22.25 μs | 23.75 μs | — |
SELECT DISTINCT |
11.44 μs | 18.27 μs | 18.36 μs | 42.53 μs |
EXCEPT |
11.41 μs | 18.13 μs | 21.57 μs | 46.40 μs |
| IN-list | 12.79 μs | 18.55 μs | 47.92 μs | 66.24 μs |
INTERSECT |
10.59 μs | 16.64 μs | 20.33 μs | 44.07 μs |
| 4-table join | 13.27 μs | 27.17 μs | 24.65 μs | 50.99 μs |
LEFT JOIN |
12.65 μs | 21.22 μs | 21.63 μs | 45.27 μs |
| Recursive CTE | 12.95 μs | 26.02 μs | 28.50 μs | — |
| String functions | 9.33 μs | 14.22 μs | 15.25 μs | 36.89 μs |
| UDF | 9.43 μs | 14.28 μs | 15.31 μs | 36.24 μs |
| Window functions | 14.99 μs | 31.84 μs | 34.65 μs | — |
The full comparison — capabilities and benchmarks, with methodology — is in the docs.
Stable — 1.0.9 is the first stable release. The public API surface is frozen and tracked from this
version (PublicAPI.Shipped.txt per package, enforced by PublicApiAnalyzers), so a breaking change
is a deliberate, reviewed diff rather than an accident. See the
roadmap and the
documentation for details.
Contributions are welcome! nextorm.extensions is designed for
third-party developers to add reusable query operators through Nextorm's public extension APIs.
You can contribute to this package or publish an independent extension package.
See CONTRIBUTING.md for guidelines.