Skip to content
AlexeyShirshovPublic

Latest commit

 

History

1,887 Commits

Folders and files

Repository files navigation

Next ORM

NuGet Build Docs License: MIT

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

Features

  • 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/SelectWhereMin automatically use PostgreSQL DISTINCT ON/LIMIT 1 and ClickHouse argMin/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. No InternalsVisibleTo, no translator registry and no global mutable state.
  • Zero-config NativeAOT — PublishAot=true is 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.

Installation

dotnet add package nextorm
dotnet add package nextorm.sqlite   # pick one provider

1.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

Quick start

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.

Query reuse

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

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 IQueryComponent and attach it with WithComponent. A component validates itself in Prepare, contributes SQL structure in BuildQuery and carries a descriptor that participates in the plan key, so a warm plan-cache hit does not rebuild it. The nextorm.extensions package 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/SelectWhereMin and SelectGroup/SelectGroupHaving are 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.

NativeAOT

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.

Benchmarks

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.

Status

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.

Contributing

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.

License

MIT