Table of Contents

SQL Server provider

Use nextorm.sqlserver for Microsoft SQL Server and Azure SQL; it renders @name parameters, top(n) / offset … fetch paging (injecting an ORDER BY when needed), + concatenation, isnull, datepart and count_big, with bracket-quoted identifiers.

Prerequisites: Provider overview · Quickstart

Overview

SqlServerDataContext (src/nextorm.sqlserver/SqlServerDataContext.cs) wraps Microsoft.Data.SqlClient. It creates a SqlConnection, passes null parameter values as DBNull (SqlClient otherwise sends no value at all), and overrides MapColumnExpression to read numeric columns without boxing: because SqlClient's typed getters are strict about widening and the column's storage type is known only once the reader is open, the mapper resolves IDataRecord.GetFieldType at runtime and reads with the matching storage-typed getter, converting with a typed Convert.To<T> (the GetNumericGetter/GetTypedConversion mapping shared with the CSV terminal). Only a storage type outside the closed numeric set falls back to GetValue/Convert.ChangeType.

SqlServerDialect (src/nextorm.sqlserver/SqlServerDialect.cs) is the dialect:

  • parameter placeholder @name;
  • identifiers are bracket-quoted (Escape returns [name]), including column references so aliases that collide with T-SQL keywords stay usable;
  • type mapping: byte→tinyint, short→smallint, int→int, long→bigint, float→real, double→float, decimal→decimal(38, 10);
  • string length is len(...), date parts are datepart(part, value), DateTime.Now/UtcNow are getdate()/getutcdate();
  • Math.Truncate becomes round(x, 0, 1) and Math.Round supplies the missing length argument: round(x, 0);
  • T-SQL has no boolean type, so a boolean-valued expression used as a value is materialised as cast(case when ... then 1 else 0 end as bit); in a condition context a boolean CASE is compared with 1.

Finally, SQL Server has no RECURSIVE keyword (recursive CTEs are declared with with alone) and exposes option (maxrecursion n) to raise the default depth.

Registering the provider

Two overloads are available on DataContextBuilder (src/nextorm.sqlserver/DI/SqlServerDataContextOptionsBuilderExtensions.cs):

using NextORM.Core;
using NextORM.SqlServer;

var byString = new DataContextBuilder()
    .UseSqlServer("Server=localhost;Database=app;Trusted_Connection=True;TrustServerCertificate=True");

using var connection = new Microsoft.Data.SqlClient.SqlConnection("Server=localhost;Database=app;...");
var byConnection = new DataContextBuilder().UseSqlServer(connection);

using var ctx = byString.CreateDataContext();   // IDataContext

Directly:

using NextORM.Core;
using NextORM.SqlServer;

using IDataContext ctx = new SqlServerDataContext("Server=localhost;Database=app;...", new DataContextBuilder());

Paging: TOP and OFFSET … FETCH

ctx.From<ISimpleEntity>().Limit(5).Select(x => x.Id);
// select top(5) id
// from simple_entity

ctx.From<ISimpleEntity>().Page(5, 10).Select(x => x.Id);
// select id
// from simple_entity order by (select null as anyorder)
// offset 10 rows
// fetch next 5 rows only

SQL Server rejects OFFSET/FETCH without an ORDER BY, so when a paged query has no sort the dialect injects the constant sort (select null as anyorder) (GetPagingOrderBy). An offset-only query emits offset m rows and no fetch. When the query already has an ORDER BY, nothing is injected.

ctx.From<ISimpleEntity>().Offset(10).OrderBy(x => x.Id).Select(x => x.Id);
// ... order by id offset 10 rows

WITH TIES is supported only in the no-offset TOP(n) WITH TIES form. A positive OFFSET together with WithTies() — for example Page(5, 3).WithTies() or Offset(3).Limit(5).WithTies() — throws NotSupportedException during preparation, before any SQL is rendered, sent or cached: T-SQL accepts WITH TIES only in TOP, not in OFFSET … FETCH … ONLY. Use WithTies() without an OFFSET, or ordinary paging without ties.

Aggregates and scalar functions

var count = ctx.From<IComplexEntity>().Select(x => SqlFunctions.Sql.count_big());        // count_big(*)
var std   = ctx.From<IComplexEntity>().Select(x => SqlFunctions.Sql.stdev((double)x.Id)); // stdev(...)

count_big / count_big_distinct render count_big(...); plain count/count_distinct render count(...). stdev, stdevp, var and varp keep their names (SQL Server provides them natively). Math.Round, Math.Truncate, len, datepart, getdate and isnull are all emitted as shown above. The variability aggregates follow the shared cardinality contract: Stdev/Var are null for zero or one contributing value, Stdevp/Varp are null for zero and 0.0 for one, and two or more values keep SQL Server's native formulas. The nullable result reaches the caller as null instead of throwing on a non-nullable double, so the same query returns the same shape here and on the other providers.

BASE64 requires SQL Server 2025: SqlFunctions.Sql.base64_encode and base64_decode render BASE64_ENCODE/BASE64_DECODE there, with no compatibility-level or preview flag. base64_decode accepts varchar input, so cast an nvarchar column. crypt_gen_random and the decimal parse/try_parse (decimal(38,18), fixed en-US) are available on the same server. nextorm does not probe the server version, so on an older server the emitted function fails with the server's own "not a recognized function" error.

SQL Server 2022+ also opts into greatest/least (standard syntax) and SqlFunctions.Sql.date_trunc, which renders datetrunc(part, value) with the plural ANSI parts folded to the singular T-SQL spellings (milliseconds → millisecond); decade/century/millennium throw. Date arithmetic is native: SqlFunctions.Sql.date_add(field, amount, value) renders dateadd(field, amount, value) (with decade/century/millennium folded onto a scaled year add), SqlFunctions.Sql.date_diff(field, start, end) renders datediff(field, start, end), SqlFunctions.Sql.date_from_parts(year, month, day) renders datefromparts(year, month, day) and SqlFunctions.Sql.end_of_month(value) renders eomonth(value).

Date and number formatting is intentionally not mapped to the cross-provider surface. T-SQL FORMAT takes a .NET format string (and depends on the CLR; SQL Server 2012+), while PostgreSQL to_char and the %-style DATE_FORMAT/strftime/formatDateTime of the other providers use incompatible template languages, so there is no portable template argument. Declare [SqlFunction("format")] for SQL Server formatting; the PostgreSQL equivalent is SqlFunctions.Postgres.to_char.

SQL Server 2017+ opts into SqlFunctions.Sql.string_agg → string_agg(value, delimiter). There is no array type, so array_agg still throws (SupportsArrayAgg is false). SQL Server 2016+ also opts into the JSON-as-text functions (SupportsTextJson): SqlFunctions.SqlServer.json_value, SqlFunctions.SqlServer.json_query, SqlFunctions.SqlServer.json_modify and SqlFunctions.SqlServer.isjson render their T-SQL names over a text column, using a JSONPath string ('$.name'); the PostgreSQL json/jsonb surface still throws. A typed OPENJSON ... WITH (...) rowset is declared with a TvfEntityBuilder<T> factory whose withClause carries the clause body (see the table-valued-functions guide). The full-text predicates SqlFunctions.Sql.contains and SqlFunctions.Sql.freetext (SupportsFullText) render as T-SQL contains(...)/freetext(...) and require a full-text index on the column. SqlFunctions.Sql.iif(condition, whenTrue, whenFalse) renders iif(...) (Iif, spelled through IIifRenderer.Render) and SqlFunctions.SqlServer.choose(index, ...) renders choose(...) (SupportsChoose); the specialized SqlFunctions.SqlServer.iif spelling still works by inheritance. Locking table hints (SupportsTableHints) render as WITH (hint, ...) after the source (after its alias when one is present): ctx.From<IComplexEntity>(o => o.WithTableHint("nolock")) emits from complex_entity with (nolock), and an aliased source emits ... as [t1] with (nolock). Row locking reuses the same mechanism: ForUpdate/ForShare (Lock, ILockRenderer.UsesTableHints) attach with (updlock)/ with (holdlock) to every physical FROM/JOIN source instead of a trailing FOR UPDATE/FOR SHARE clause (FromOptions.ForUpdate/JoinOptions.ForUpdate target a single source). A LockWaitMode adds nowait or readpast to the same hint (with (updlock, nowait) / with (updlock, readpast)); readpast approximates SKIP LOCKED. QueryCommand.ForJson(...) (SupportsForJson) is a terminal that executes the query and returns the whole result set as one JSON document (FOR JSON PATH/FOR JSON AUTO, with optional ROOT('...') and INCLUDE_NULL_VALUES), and QueryCommand.ForXml(...) (SupportsForXml) does the same for FOR XML RAW/AUTO/EXPLICIT/PATH (with optional row element, ROOT('...') and ELEMENTS). QueryCommand.WithForJson(...)/WithForXml(...) attach the clause without executing. The session/information family (SessionInfoFunctions) renders SqlFunctions.Sql.current_user()/session_user() as the ANSI key words and current_schema()/current_database()/version() as schema_name()/db_name()/@@version. SQL Server renders the window percentiles SqlFunctions.Sql.percentile_cont(fraction, property).Over() and percentile_disc(...) as percentile_cont(fraction) within group (order by property) over (...) (SupportsPercentileWindow); it has no exact ordered-set aggregate form. The median is the same native composition: SqlFunctions.SqlServer.median(value).Over(...) renders percentile_cont(0.5) within group (order by value) over (...), one row per input row (not one row per group); only a partition-only OVER is supported, so a window ORDER BY or a frame is refused, and the call must be completed with Over(...). The generic SqlFunctions.SqlServer.percentile has no distinct native spelling and throws NotSupportedException. Fractions are double values in [0, 1], not percentages, and are validated before rendering. The arbitrary-value aggregate any_agg (ANY_VALUE) is not enabled: T-SQL exposes ANY_VALUE only on SQL Server 2025 / Fabric, which the version-agnostic dialect cannot assume. The XML data-type methods SqlFunctions.SqlServer.xml_value(xml, xpath, sqlType), xml_query(xml, xpath) and xml_exist(xml, xpath) (XmlFunctions) render the postfix T-SQL form xmlcol.value('xpath', 'type') / xmlcol.query('xpath') / xmlcol.exist('xpath'); the XQuery and the SQL type must be string literals. The rowset method .nodes is exposed as SqlFunctions.SqlServer.xml_nodes(xml, xpath) and used as a correlated CrossApply/OuterApply source: it renders <xml>.nodes('xpath') as [alias]([value]) and the unfolded IXmlNodesRow.Value is projected with the scalar methods above (the operand must be an outer row column, the XQuery a string literal).

Beyond those, SQL Server contributes a broad T-SQL-only scalar library on SqlFunctions.SqlServer, gated per name by ISqlServerFunctions and rejected by every other provider:

  • Date/time: the clock/offset family sysdatetime/sysdatetimeoffset/sysutcdatetime, switchoffset/todatetimeoffset (string offset or signed minutes) and the construction family timefromparts/smalldatetimefromparts/datetimefromparts/datetime2fromparts/ datetimeoffsetfromparts (the *FROMPARTS precision must be a constant 0..7);
  • Binary/checksum: checksum/binary_checksum (at least one argument; the checksum(*) wildcard is not exposed), compress/decompress (GZIP over varbinary(max)) and the pseudo-random rand/rand(seed);
  • Metadata: col_length, col_name, ident_incr/ident_seed, index_col, object_definition/object_id/object_name/object_schema_name, stats_date, db_id/db_name/original_db_name, schema_id/schema_name, type_id/type_name, filegroup_id/filegroup_name/file_id/file_idex/file_name, current_timezone/ current_timezone_id, getansinull, parsename and publishingservername;
  • String/conversion: stuff and str; isdate/isnumeric return the native T-SQL int (1/0), not bit; formatmessage accepts a format string or a sys.messages id and at most 20 arguments.

The seven names that report live, mutable connection-, session- or statement-scope state are exposed as-is: CURRENT_REQUEST_ID, CURRENT_TRANSACTION_ID, XACT_STATE, IDENT_CURRENT, MIN_ACTIVE_ROWVERSION, ROWCOUNT_BIG and SCOPE_IDENTITY. Live mutable state is not an exclusion criterion: the native SQL Server semantics are shipped with their caveats, and nextorm promises no per-row stability, no connection continuity between queries and no identity-retrieval guarantee — a separate SCOPE_IDENTITY/IDENT_CURRENT call is not a last-insert API, use ReturningIdentity<TKey>() instead. Stable configuration and identity constants of the same scope (CURRENT_TIMEZONE/ CURRENT_TIMEZONE_ID, SERVER_NAME, HOST_ID, HOST_NAME, APP_NAME, SUSER_NAME, ORIGINAL_LOGIN) stay on the same surface. See the T-SQL scalar functions guide.

Recursive CTEs and maxRecursion

// Union branches must share one result type, so a named shape is used.
public sealed class CteNumberRow { public int n { get; set; } }

var anchor = e.Where(s => s.Id == 1).Select(s => new CteNumberRow { n = s.Id });
var step = ctx.From("nums").Where(t => t["n"].AsInt < 5).Select(t => new CteNumberRow { n = t["n"].AsInt + 1 });
var body = anchor.UnionAll(step);

var sql = ctx.WithRecursive("nums", body, 100).From("nums")
    .Select(t => new CteNumberRow { n = t["n"].AsInt });
with nums as (
    select ... union all select ...
)
select n
 from nums option (maxrecursion 100)

The recursive modifier is omitted (T-SQL has none) and maxRecursion is rendered as the trailing statement option; SQL Server's default depth is 100, so only pass a value when you need a different limit.

*ALL set operations

SQL Server has neither INTERSECT ALL nor EXCEPT ALL. Calling IntersectAll or ExceptAll throws a NotSupportedException from the dialect before any SQL reaches the database; Intersect and Except (without ALL) work.

// Throws NotSupportedException mentioning IntersectAll.
var q = a.Select(x => x.Id).IntersectAll(b.Select(x => x.Id));

Aliases

Derived tables (a subquery in FROM) and table-valued functions must be aliased, and identifiers use brackets:

select t1.value, t2.somestring as [String]
 from all_rows() as [t1]
 join complex_entity as [t2] on t1.id = t2.id

Table-valued parameters

SQL Server binds a table-valued parameter natively through a user-defined table type: pass the type name with ProcedureParameter.Table(name, typeName, rows). The rows are streamed as SqlDataRecord (one reused record per enumeration), so a large set is never buffered. A scalar row type maps to a single column; an entity row type maps its non-computed columns in metadata order, including identity columns. A decimal column uses the precision/scale declared with [DecimalPrecision(p, s)] or the fluent mapping, defaulting to decimal(38,18). An empty sequence is bound as an empty table. See Raw SQL.

create type dbo.IdList as table (value int not null);
using var result = dataContext.ExecuteRaw(
    "select sum(value) as total from @p",
    [ProcedureParameter.Table("p", "dbo.IdList", new[] { 1, 2, 3 })]);

var total = result.Read<int>()[0];   // 6

The CLR type of each column picks a fixed T-SQL type; the user-defined table type must match it by ordinal:

CLR T-SQL
bool bit
char nchar(1)
sbyte, short smallint
byte tinyint
ushort, int int
uint, long bigint
ulong decimal(20,0)
float real
double float
decimal decimal(38,18) (or the declared decimal(p,s))
string nvarchar(max)
Guid uniqueidentifier
DateTime datetime2
DateTimeOffset datetimeoffset
DateOnly date
TimeOnly time
TimeSpan bigint (mapped duration unit, or ticks)
byte[] varbinary(max)

A decimal column's precision/scale comes from [DecimalPrecision] or the fluent mapping (default decimal(38,18)). For a different precision/scale on any other type or a string length other than max, declare the table type explicitly and pass a legacy DataTable with ProcedureParameter.TypeName. An empty sequence is bound as an unset parameter, which SqlClient sends as an empty table.

Large objects (LOB streaming)

A single byte[] or string column can be read as a Stream/TextReader without loading the value into managed memory. SqlClient implements GetStream/GetTextReader for varbinary(max) and nvarchar(max); the streaming mode requires CommandBehavior.SequentialAccess (without it the value is buffered), which nextorm sets on the LOB path:

await using var stream = ctx.From<BinaryEntity>()
    .Where(x => x.Id == 1)
    .Select(x => x.Payload)
    .ToStream();          // opens the reader with CommandBehavior.SequentialAccess

using var reader = ctx.From<Document>()
    .Where(x => x.Id == 1)
    .Select(x => x.Body)
    .ToTextReader();

The returned stream owns the reader and the per-call command until it is disposed, and it does not close the context; the projection must be exactly one byte[]/string column (otherwise InvalidOperationException). MySQL/MariaDB, ClickHouse and the in-memory provider reject the terminals with NotSupportedException. See Streaming large objects.

SQL Server also supports the multi-column ToDataReader/ToDataReaderAsync terminal: it hands the same sequential-access command over as a caller-owned DbDataReader, so the caller can read every column and row (or several LOB columns in order) without materialising the result. SQLite supports it too, but via a buffered, locator-free reader (no rowid, no SequentialAccess, so no chunked LOB); MySQL/MariaDB, ClickHouse and the in-memory provider have no sequential-access support and reject it.

Bulk insert

CreateBulkInsertBuilder writes through the native SqlBulkCopy path when Mode is Auto (the default) and every requested option is compatible; Native refuses by name instead of falling back, and Portable writes INSERT ... VALUES (the only path for Returning*).

  • MaxBatchSize maps to SqlBulkCopy.BatchSize; unset stays the driver default (0, one batch).
  • KeepIdentity maps to SqlBulkCopyOptions.KeepIdentity; the portable statement is wrapped with SET IDENTITY_INSERT ... ON/OFF instead.
  • CheckConstraints/TableLock/KeepNulls/FireTriggers map to the matching SqlBulkCopyOptions flags; false is honoured by not requesting the flag, and KeepNulls=false is accepted only for a default-equivalent shape.
  • The typed group carries the internal transaction: SqlServer(new SqlServerBulkInsertOptions { UseInternalTransaction = true }) sets SqlBulkCopyOptions.UseInternalTransaction for the driver batch. It cannot be combined with an external context transaction, covers only the current driver batch when several are used, and is refused on the portable path.
  • Timeout is one whole-invocation budget: the remaining time becomes BulkCopyTimeout (minimum 1 s), and an expired budget surfaces as TimeoutException; the caller's cancellation always wins.
  • DatabaseName renders as part of the multipart name; ServerName is refused (a bulk insert never reroutes to another server).

The set is buffered in a DataTable before the copy, because SqlBulkCopy needs column type metadata; a chunk limit splits driver batches but not that buffer. See Bulk insert.

Native json column (SQL Server 2025)

SQL Server 2025 (17.x) introduced a distinct built-in json type: the server reports it as json in the column metadata, it is not an alias of nvarchar, and it works under every database compatibility level. nextorm does not detect it — the capability is a pure function of the server version and engine edition you declare, so SQL generation never depends on the connection, the compatibility level or a metadata round-trip.

Context construction SupportsJson [JsonColumn] storage on a json column
new SqlServerDataContext(connectionString, builder) — default, unversioned false text only: Auto/Text write nvarchar, Native throws NotSupportedException
new SqlServerDataContext(new Version(16, 0), connectionString) — SQL Server 2016–2022 false same as the unversioned context: text only, Native refused before any command runs
new SqlServerDataContext(new Version(17, 0), connectionString) — SQL Server 2025+ true json: Auto/Text/Native all resolve to the same text converter
new SqlServerDataContext(SqlServerEngineEdition.SqlDatabase, connectionString) — Azure SQL Database true edition-derived: the engine reports major version 12, so the edition must be declared (Auto/Text/Native resolve to the same text converter). Unit-only: no live Azure instance was available
…AzureSqlManagedInstance / …MicrosoftFabricSqlDatabase — Azure SQL Managed Instance / Fabric SQL database true same as above; Fabric SQL database is edition 12 — edition 11 ("Azure Synapse serverless SQL pool, or Microsoft Fabric") does not enable it. Unit-only

The edition is a caller declaration: nextorm never reads SERVERPROPERTY('EngineEdition'), so SQL generation stays a pure function of the declared state. A concrete context type owns one immutable (version, edition) declaration — the plan cache is keyed by the context type — and a conflicting declaration throws InvalidOperationException before any command runs. Declaring the capability does not migrate an nvarchar column to json, and Auto keeps the text transport.

// Version-first constructor: the declared version opens the version-gated capabilities.
using var dataContext = new SqlServerDataContext(new Version(17, 0), connectionString);

(Version, DbConnection) is the second overload, for a caller-owned connection. A context type owns one immutable (version, engine edition) declaration — the plan cache is keyed by the context type — so constructing the same type with a conflicting declaration of either component (a different version, a different engine edition, or unversioned and then declared) throws InvalidOperationException before any command runs; derive a distinct SqlServerDataContext subclass per declaration.

Transport and limits

  • The representation is the ordinary CLR string: parameters are inferred as nvarchar, there is no DOM parameter, no SqlDbType.Json and no double serialisation.
  • On a context whose gate is open — by declared major version 17+, or by a declared Azure/Fabric SQL database edition — Auto, Text and Native resolve to the same converter, so an existing nvarchar-backed [JsonColumn] stays byte-for-byte identical when the version or the edition argument is added.
  • A native json column stores object and array documents only. Scalar roots (string, number, boolean) and the JSON null document are rejected by the server with error 13609 — use Text storage for those payloads. This is a server limitation, not a library refusal, and nextorm adds no client-side validation for it.
  • JSON null in text storage is the four-character text null (DATALENGTH 8 in nvarchar), which stays distinct from SQL NULL.

Migration

nextorm does not generate column DDL, so switching a column from nvarchar(max) to json is a schema change you make in the database — for example alter table t alter column payload json in a release step. The library side keeps working either way: the same [JsonColumn] mapping writes the same text to either column type, and Auto never retargets an existing mapping on its own. Declaring an engine edition is equally a library-side declaration: it never alters a column.

Provider differences

Aspect SQL Server
Parameter placeholder @name
Limit only top(n)
Limit + offset offset m rows fetch next n rows only
Offset only offset m rows
Paging without ORDER BY injects order by (select null as anyorder)
Concat +
Coalesce isnull
Boolean literal 1 / 0 (bit materialisation)
Identifier quoting brackets (as [t1])
Derived table / TVF alias required
LOB streaming (ToStream/ToTextReader, ToDataReader) supported (SequentialAccess; single byte[]/string column, or a multi-column caller-owned reader)
*ALL not supported (throws)
Recursive CTE with + option (maxrecursion n)
Aggregate names stdev/var native; count_big available
greatest / least supported (SQL Server 2022+; ignores NULL arguments)
date_trunc datetrunc(part, value) (SQL Server 2022+)
date_add / date_diff / date_from_parts / end_of_month dateadd(field, amount, value) / datediff(field, start, end) / datefromparts(y, m, d) / eomonth(value)
string_agg / array_agg string_agg supported (SQL Server 2017+); array_agg not supported (throws)
Text JSON json_value / json_query / json_modify (SQL Server 2016+)
Conditional functions iif(...) (portable, Iif) / choose(...) (SupportsChoose)
Full-text predicates contains(...) / freetext(...) (column must be full-text indexed)
Full-text ranking containstable(table, column, search) / freetexttable(...) table functions return KEY/RANK (SqlFunctions.SqlServer, IKeyRankRow<TKey>)
Regular expressions regexp_like(value, pattern, 'c'/'i') / regexp_replace(value, pattern, replacement, 1, 0, 'c'/'i') (SQL Server 2025+; regexp_like additionally needs database compatibility level 170)
Locking table hints with (hint, ...) after the source, after its alias when aliased (WithTableHint)
Row locking ForUpdate/ForShare render with (updlock)/with (holdlock) on every physical FROM/JOIN source; FromOptions.ForUpdate/JoinOptions.ForUpdate target one source (Lock, ILockRenderer.UsesTableHints); a LockWaitMode adds nowait/readpast (with (updlock, nowait)/with (updlock, readpast))
Native bulk copy SqlBulkCopy; BulkInsertOptions CheckConstraints/TableLock/KeepNulls/FireTriggers map to SqlBulkCopyOptions, and SqlServerBulkInsertOptions.UseInternalTransaction sets the internal-transaction flag (see Bulk insert)
Session/info functions current_user, session_user, schema_name(), db_name(), @@version
T-SQL clock/offset/*FROMPARTS sysdatetime / sysdatetimeoffset / sysutcdatetime / switchoffset / todatetimeoffset / timefromparts / smalldatetimefromparts / datetimefromparts / datetime2fromparts / datetimeoffsetfromparts (per-name gated)
T-SQL binary/checksum/other checksum / binary_checksum / compress / decompress / rand / stuff / str / isdate / isnumeric / formatmessage (per-name gated)
T-SQL metadata col_length / col_name / object_id / object_name / db_id / db_name / schema_id / schema_name / type_id / type_name / file_id / file_name / current_timezone / parsename / stats_date and the rest of the A-D families (per-name gated)
Window percentiles percentile_cont/percentile_disc as ... within group (order by x) over (...) (SQL Server 2012+)
Median (analytic) SqlFunctions.SqlServer.median(v).Over(...) → percentile_cont(0.5) within group (order by v) over (...): one row per input row; partition-only OVER (ORDER BY/frame refused)
Generic percentile not supported: SqlFunctions.SqlServer.percentile throws NotSupportedException
Arbitrary-value aggregate not supported (ANY_VALUE is SQL Server 2025 / Fabric only)
JSON output whole result set as one JSON document, terminal for json path / for json auto (ForJson)
XML output whole result set as one XML document, terminal for xml raw/auto/explicit/path (ForXml)
XML data-type methods xml.value('xpath', 'type') / xml.query('xpath') / xml.exist('xpath') / xml.nodes('xpath') as [alias]([value]) (XmlFunctions)
AVG over an integer column truncated to an integer
ORDER BY … DESC null placement nulls sort last by default

See also


Source: tests/nextorm.sqlserver.tests/Dialect/SqlServerDialectTests.cs:25,37,43,52,60,69,84,93,102, tests/nextorm.sqlserver.tests/SqlGeneration/SqlGenerationTests.cs:133,146,160,185,218,231,249,268,856,1044, tests/nextorm.integration.tests/SqlServer/SqlServerSpecificTests.cs:24,43, src/nextorm.sqlserver/SqlServerDialect.cs, src/nextorm.sqlserver/SqlServerDataContext.cs, src/nextorm.sqlserver/DI/SqlServerDataContextOptionsBuilderExtensions.cs.