SQL Server provider
Use
nextorm.sqlserverfor Microsoft SQL Server and Azure SQL; it renders@nameparameters,top(n)/offset … fetchpaging (injecting anORDER BYwhen needed),+concatenation,isnull,datepartandcount_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 (
Escapereturns[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 aredatepart(part, value),DateTime.Now/UtcNowaregetdate()/getutcdate(); Math.Truncatebecomesround(x, 0, 1)andMath.Roundsupplies 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 booleanCASEis compared with1.
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 familytimefromparts/smalldatetimefromparts/datetimefromparts/datetime2fromparts/datetimeoffsetfromparts(the*FROMPARTSprecision must be a constant0..7); - Binary/checksum:
checksum/binary_checksum(at least one argument; thechecksum(*)wildcard is not exposed),compress/decompress(GZIP overvarbinary(max)) and the pseudo-randomrand/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,parsenameandpublishingservername; - String/conversion:
stuffandstr;isdate/isnumericreturn the native T-SQLint(1/0), notbit;formatmessageaccepts a format string or asys.messagesid 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*).
MaxBatchSizemaps toSqlBulkCopy.BatchSize; unset stays the driver default (0, one batch).KeepIdentitymaps toSqlBulkCopyOptions.KeepIdentity; the portable statement is wrapped withSET IDENTITY_INSERT ... ON/OFFinstead.CheckConstraints/TableLock/KeepNulls/FireTriggersmap to the matchingSqlBulkCopyOptionsflags;falseis honoured by not requesting the flag, andKeepNulls=falseis accepted only for a default-equivalent shape.- The typed group carries the internal transaction:
SqlServer(new SqlServerBulkInsertOptions { UseInternalTransaction = true })setsSqlBulkCopyOptions.UseInternalTransactionfor 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. Timeoutis one whole-invocation budget: the remaining time becomesBulkCopyTimeout(minimum 1 s), and an expired budget surfaces asTimeoutException; the caller's cancellation always wins.DatabaseNamerenders as part of the multipart name;ServerNameis 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 asnvarchar, there is no DOM parameter, noSqlDbType.Jsonand 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,TextandNativeresolve to the same converter, so an existingnvarchar-backed[JsonColumn]stays byte-for-byte identical when the version or the edition argument is added. - A native
jsoncolumn stores object and array documents only. Scalar roots (string, number, boolean) and the JSONnulldocument are rejected by the server with error 13609 — useTextstorage for those payloads. This is a server limitation, not a library refusal, and nextorm adds no client-side validation for it. - JSON
nullin text storage is the four-character textnull(DATALENGTH8 innvarchar), which stays distinct from SQLNULL.
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.