Reference
Version history
Every version of the ARX.SDK packages, newest first, and what each one brings.
Every version of the ARX.SDK packages, newest first. All six packages, the core ARX.SDK and the providers
ARX.SDK.PostgreSql, ARX.SDK.SQL, ARX.SDK.MySQL, ARX.SDK.MongoDb and ARX.SDK.DynamoDb, are released together
with one version, so a provider always works with the core of the same version. Versions follow
Semantic Versioning: a preview (-preview.N) may change anything from one preview to the next;
before 1.0 a release's breaking change is a minor version; from 1.0 only a major version breaks. Every change that can
break code is listed here under Breaking, with what to do (the Versions and compatibility guide has the whole
policy). Each package also carries its own CHANGELOG.md with the same entries for that package.
| Version | Released | Status |
|---|---|---|
| 1.0.0-rc.1 | 2026-11-12 | Release candidate |
| 0.1.0-preview.5 | 2026-11-12 | Preview |
| 0.1.0-preview.4 | 2026-11-10 | Preview |
| 0.1.0-preview.3 | 2026-11-10 | Preview |
| 0.1.0-preview.2 | 2026-11-10 | Preview |
| 0.1.0-preview.1 | 2026-11-09 | Preview |
1.0.0-rc.1
Release candidateReleased: 2026-11-12 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
The API freeze for 1.0 (docs/33): the public surface is fixed here, and only fixes follow until 1.0.0. One id type in signatures, provider plumbing in its own namespace, legacy members removed, PascalCase join names, and every public member annotated for nullable reference types. ARX.SDK 1.0.0 follows after two to four weeks of use, unless a fix needs a change the freeze can't take.
Breaking
Every change below fails at compile time; none changes what a call does. The before and after, in one table:
| Before | After |
|---|---|
RetrieveAsync("t", 42L), DeleteAsync("t", guid), DeleteAsync("t", "PH") |
unchanged: the id converts to ArxRecordId (the long, Guid and string copies of each call are removed) |
DeleteMultipleAsync("t", new List<long> { 1, 2 }) |
unchanged: one generic DeleteMultipleAsync<TId> takes a list of long, int, Guid, string or ArxRecordId |
ArxJoinOperator.INNER / LEFT / RIGHT / DEFAULT |
Inner / Left / Right / Default (same values) |
new ArxColumnSet(true) |
ArxColumnSet.All |
new ArxColumnSet(false) |
new ArxColumnSet() (an empty set reads every column, as before) |
entity.FormattedValues[...] |
removed (no provider filled it): an option's label is entity.GetSelectionSetDisplayText(...), a lookup's name ArxEntityReference.Name |
ArxDateTimeZoneIndependent |
ArxDateTimeConditionOption (it behaved the same) |
DeleteMultipleAsync(new ArxDeleteMultipleParameter { … }) (SQL providers) |
DeleteMultipleAsync(table, ids) |
MTDataCollection<TKey, TValue> |
removed: ArxAttributeCollection (entity.Attributes) keeps every member it had |
ArxInjectionCheck, ArxFilterWrite, ArxPreLoadTypes, ArxProviderCapabilities, ArxBatchScope |
namespace ARX.SDK.Providers (for provider authors; an application uses none of them) |
the interop overloads' _connection: / _transaction: named arguments |
connection: / transaction:, and every interop overload ends with a CancellationToken |
a class of your own implementing IArxDataService or IArxBatchScope |
removes the typed-id members and implements DeleteMultipleAsync<TId> |
Added
DeleteMultipleAsync<TId>(table, ids)(and with anArxTransaction) on every service,IArxDataServiceandIArxBatchScope: one method for every id type; another element type is ARX1007 UnsupportedValueType.ArxColumnSet.All: every column, a new set each time.- Nullable annotations on every public member of the six packages (
<Nullable>annotations</Nullable>): a?marks what can be null.ArxEntityReference.Nameisstring?;ArxPagingInfo.NextreturnsArxPagingInfo?. - A reflection test of the core and a conformance test of each provider pin the public surface.
0.1.0-preview.5
PreviewReleased: 2026-11-12 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
Writes by a filter and creates without ids on every provider (UpdateMultipleAsync(values, filter),
DeleteMultipleAsync(table, filter), ReturnIds = false), strong-named assemblies, and the fixes from ten rounds of
adoption evaluation (2026-11-11): data-integrity fixes (below, first), the same batch-scope code on every provider, a
commit or a write whose outcome is unknown reported as not safe to retry, one paging loop for every provider
(ArxPagingInfo.Next), clearer errors, and IntelliSense that shows only the public API. Read Breaking before
upgrading.
Fixed: data integrity
- A commit whose connection failed or timed out after it was sent was reported as rolled back (
StateRolledBack, a transient ARX7005), though the database may have kept the work, so retry code could save it twice. Such a commit is nowOutcomeUnknown(onArxTransactionand on a batch scope), and its error isn't transient: check the database before doing the work again. A commit the database refuses (a deadlock, a serialization failure) is still rolled back and transient. - PostgreSQL, SQL Server and MySQL: a Retrieve by id could return another read's columns or another table's row. The
statement each service keeps per table and column list was found by a key that joined the names with the text
0001, so a name holding0001could match another read's key (tableacctwith column0001name, and tableacct0001name). Each name is now written with its length, so two different reads never share a key. GetAttributeValue<double>(and<float>) of a decimal, or of a long past 2^53, dropped digits without a word (123456789012345.1234 read as 123456789012345.11), though numbers convert only when nothing is lost. It is now ARX4001 ValueOutOfRange; values a double holds exactly (12.5, 0.1) still convert.- PostgreSQL, SQL Server and MySQL: a query that groups by a link's column and lists that column in the link's
column set (
link.ColumnSet.AddColumn("name")withAddGroupByColumn("r.name")) was refused as ARX1004 DuplicateAttribute since 0.1.0-preview.4; the column is read once. - DynamoDB and MongoDB: a column the catalog declares as a date, time, Guid or bool (a preload or
.Column) refuses a value of another type with ARX4004, in a write and in a condition. Text such as"yesterday"was stored in aDateOnlycolumn, and every later read of the item failed with ARX5003. Text is never parsed as a date or time; a Guid column takes aGuidor text that is one. RunBatchAsyncreports a scope a failed call rolled back (ARX1015) also when the scope is wrapped in a decorator, as the production guide suggests for auditing; it checked ARX's own scope class only, and a wrapped one returned silently.- MongoDB: a write outside a transaction that is cancelled while the server is already running it (for example blocked behind another transaction) is no longer reported as cancelled: the server applies it anyway, so ARX now honours the token until the write is sent and then waits for the outcome. Commits work the same way. Inside a transaction a cancelled write still fails the call and nothing of the transaction is kept.
- A declared version column is read with every record, as the id is, even when the column set doesn't name it: an entity read, changed and updated carries the version it read, so a concurrent change is ARX2004 instead of a lost update (twenty stale updates all succeeded when the read didn't ask for the version).
- PostgreSQL, SQL Server, MySQL and DynamoDB: an update or delete that finds its rows by a declared key or by columns
(
ArxMatch.Key,ArxMatch.Columns) checks the declared version column, as one by id always did. It wrote a stale version over the current one and deleted changed rows (twenty concurrent updates with the same version all succeeded). Now a stale version is ARX2004 RecordChanged and nothing is written. RunBatchAsync: a body that catches a failed call and returns, after that call rolled the whole scope back (no savepoints: MongoDB, DynamoDB, orSavepoints = false), throws ARX1015 TransactionAborted instead of returning as if the work were saved. With savepoints the failed call is undone alone and the rest commits, as before.- SQL Server: a
DateTimeOffsetis written as its UTC instant whatever ARX knows about the column, so adatetime2column with a hand-written catalog holds the right moment (it held the clock time, hours off). - PostgreSQL, SQL Server and MySQL, with the column types preloaded: a number with a fraction written to a whole-number
column (7.9 into an
int) was rounded or truncated by the database without a word. A create, an update and a write by a filter now refuse it before the database, ARX4001 ValueOutOfRange; a condition in a read (qty < 7.5) is still sent. NaN and the infinities are refused the same way for a declareddecimalcolumn. - PostgreSQL: a
numericholding NaN, Infinity or -Infinity made every read of its row fail with a rawInvalidCastException(a .NETdecimalcan't hold them). They now read back as adouble. - MongoDB and DynamoDB: field names are case-sensitive there, so an update with
entity["name"]on a document holdingNameadded a second field, and the document could then not be read with all columns (ARX1004). A field the catalog declares (the id, a key, the version, the display name, a preloaded or declared column) is now always written and read with its declared spelling; an undeclared field keeps its exact spelling, as documented. - Create ignored a
RecordIdset by the caller (XRM code setsentity.Idbefore a create) and the database made a new id. It is now ARX1001 unless the id attribute holds the same id; a record ARX read or created can still be created again as a copy. A conformance test checks it on every provider. - MongoDB: a write outside a transaction whose connection failed or timed out after it was sent (a write blocked by a
lock past the socket timeout), or whose write concern failed, was ARX7005 with
IsTransienttrue, and the server could apply it later, so a retry policy could apply it twice. Its outcome is unknown: it is now ARX7005 (ARX7003 for a server time limit) withIsTransientfalse and a message that says so. A failure before anything was sent stays transient.
Breaking
- Create refuses a
RecordIdyour code set (ARX1001) unless the id attribute holds the same id: set the id attribute to give a record its id. A record ARX read or created keeps working as a copy. - DynamoDB: a catalog
.Lookupto a table declaredNoIdColumnis ARX5003 when the service is made; the lookup was left as its raw value without a word. - MongoDB and DynamoDB: an id of another type than the declared id (
RetrieveAsync("customer", 42L)for a Guid id) was ARX4004 DataTypeMismatch, where the SQL providers give ARX1001 InvalidArgument, with the same message. It is ARX1001 on every provider, for Retrieve, Update and Delete by id and a list of ids; a create's id value of the wrong type stays ARX4004, as any column value. DynamoDB looked such an id up without a word (no item found); it is now refused. A conformance test checks it. ArxTransactionStateandArxBatchStatehave a new value,OutcomeUnknown: aswitchover them adds it.IArxDataServiceandIArxBatchScopehaveUpdateMultipleAsync(values, filter),DeleteMultipleAsync(table, filter)andCreateMultipleAsync(entities, ArxCreateMultipleOptions)(each also with anArxTransaction): a class of your own that implements them adds them (a decorator built withDispatchProxyneeds nothing).IArxDataServicehasBeginTransactionAsync: a class of your own that implements the interface adds it (a decorator built withDispatchProxyneeds nothing).- A record read from a table declared
ArxIdColumn.NoIdColumnhasRecordIdArxRecordId.None, as Create returns, not null (PostgreSQL, SQL Server, MySQL, DynamoDB; MongoDB refuses such tables). TestRecordId.HasValue, not null. - SQL Server: a
DateTimeOffsetis stored as its UTC instant in every setup (with a preload or no catalog the offset given was kept): one rule, as PostgreSQL and MySQL, which keep no offset either. ArxEntityReference.Idand.Guidare obsolete:Idread 0 for a Guid or text id. UseRecordId. SettingGuidnow sets the id.ArxRecordIdequality ignores the column when one side has none ("abc"equals the id Create returned for "abc"), and==compares values.- SQL Server: a
DateTimeOffsetwritten to adatetimeoffsetcolumn of a table ARX hasn't preloaded is stored with offset +00:00 (the same moment; the offset given was kept before). A preloaded schema keeps the offset. GetAttributeValue<T>converts numbers when nothing is lost and reads a lookup or a record id as its id; a value it can't give asTis ARX4004 DataTypeMismatch (a number that doesn't fit, ARX4001), anArxValidationException, where it threwInvalidCastException.ArxJoinOperatorandArxDateTimeConditionOptionTypemoved fromARX.SDK.MetadatatoARX.SDK.Query, with the other query types: replaceusing ARX.SDK.Metadata;withusing ARX.SDK.Query;.IArxDataServicehas four more members (Retrieve, RetrieveMultiple and Execute in anArxTransaction); a class of your own that implements it needs them.- Batch scopes have no savepoints unless asked for, on every provider, so the same code ends the same way on every
database: a failed call rolls the scope back, and
RunBatchAsyncthrows ARX1015 when a body that caught the error returns. Ask for savepoints (new ArxBatchOptions { Savepoints = true }) where a failed call should be undone alone and the rest kept: PostgreSQL, SQL Server and MySQL have them (capabilityArxCapability.Savepoints); MongoDB and DynamoDB refusetrue(ARX8001).ArxBatchOptions.Savepointsis abool?(null = none). PostgreSQL, SQL Server and MySQL had savepoints by default: code that caught a failed call in a scope and went on must now ask for them. ArxQueryExpression,ArxProcedureExpression,ArxColumnSetandArxEntityCollectionno longer implementIDisposable: they hold nothing to release. Remove ausingaround them.- MongoDB: a query of an undeclared collection that doesn't exist is ARX5001 UndefinedEntity, as on the SQL providers, where it read as an empty result (a misspelt name looked like "no rows").
Added
ArxPagingInfo.Next(page): the paging for the page after this one, by the cookie where the provider returned one and by the page number where it didn't, null after the last page. One loop then reads every page on every provider; a loop that only passed the cookie on read the first page again forever on PostgreSQL, SQL Server and MySQL.ArxSchemaCatalogBuilder.DeclaresTable,DeclaresKeyandDeclaresColumn: whether a declaration is already made.- Writes by a filter:
UpdateMultipleAsync(values, filter)sets the entity's attributes on every row the filter matches and returns how many;DeleteMultipleAsync(table, filter)deletes them. One statement on PostgreSQL, SQL Server and MySQL,updateMany/deleteManyin one transaction on MongoDB; on DynamoDB the filter must give a partition key (one query, then one transaction of at most 100 items, each conditioned on the filter still matching). The entity carries only the values: an id, a version value, an empty filter (unlessArxFilterWriteOptions.AllRows) or a condition on a link is ARX1001. A version column goes up by one on every changed row. CapabilityWriteByFilter, declared by all five. - CreateMultiple without ids:
CreateMultipleAsync(entities, new ArxCreateMultipleOptions { ReturnIds = false })reads no ids back and returnsArxRecordId.Nonefor each, all or nothing as before. PostgreSQL loads a group of 5,000 rows or more of one shape with binaryCOPY, SQL Server one of 1,000 or more withSqlBulkCopy(constraints and triggers checked), and smaller groups take the ordinary statement, which was faster below those sizes; MySQL, MongoDB and DynamoDB keep their ordinary insert. Without a catalog, a table with no usable primary key can be created this way (not ARX1019). IArxDataService.BeginTransactionAsync(): a transaction on a connection of its own, the same way on every provider, so code written against the interface needs no provider type. Disposing it rolls back an uncommitted transaction and closes the connection. In the shared conformance suite.- The assemblies are strong-named (public key token
e3d5196710135cb8), the obfuscated providers included: they are signed again after obfuscation, and the pack refuses an assembly without a valid strong name. - Batch scopes are traced: each call in a scope records its activity and the operation metrics, as outside one.
RunBatchAsync<T>, for a body that returns a value (returned after the commit).IArxDataService:RetrieveAsync,RetrieveMultipleAsyncandExecuteAsyncin anArxTransaction(DynamoDB checks the transaction and reads as usual: its writes are sent at commit).- The providers'
RetrieveAsyncis annotatedTask<ArxEntity?>, so a caller with nullable enabled is warned before using a record that may not be there. ArxRecordIdconverts from along,int,Guidorstring:RetrieveAsync("country", "DE")takes the id column from the table's declaration.- An undeclared table's ARX1016 names the closest declared table when there is one ("Did you mean 'account'? Table names
are matched exactly."), from
ArxSchemaCatalog.ClosestTable(name). - Capability
ArxCapability.Savepoints.
Fixed
- MySQL: a value too long for its column was ARX4002 in a single-row write but ARX4003 in a multi-row update (MySQL reports error 1265 there). It is ARX4002 in both; an ENUM or SET value the column doesn't have stays ARX4003.
- DynamoDB: a batch scope refused
RetrieveAsyncby id (ARX8001) while it read by entity and by query; it reads by id too (what is committed: the scope's writes are sent at commit). - Documentation: a function read is read-only only on a connection ARX opens (inside your transaction a function that
writes does write);
ArxColumnSet(false)and an empty column set read every column (XRM's read only the id);FormattedValuesis never filled by a provider; a literalnullfor the options of a write by a filter is CS0121; the AI guides onRecordId(a class: pass it, not.Value); the specification on errors that aren't transient because an outcome is unknown. GetAttributeValue<DateOnly>of aDateTimeat midnight gives its date (MongoDB stores aDateOnlythat way, and it was ARX4004); one with a time of day is ARX4001. The ARX4001 hint for a fraction read as an integer says to read it as decimal or double (it said long).- The MySQL samples' schema script never drops a database: it creates
arx_samples_reportingwhen it is missing and replaces the one view in it. - Documentation: the specification's ARX2004 class (an
ArxDatabaseException; it said the base class) and its rule for letter case in a document store; the SQL Create pages' row for a service without a catalog (it resolves the table, rather than ARX1016); the AI guides and skill no longer call a name from a user safe to pass (a valid name can read any table the database user can: use an allow list or Strict), and say that rows read without a catalog have noRecordId; letter case on MongoDB and DynamoDB in the switching guide; the portable paging loop withNext; the Create rule forRecordIdin the XRM guide; the MySQL README's description of its script. GetAttributeValue<DateTimeOffset>of a UTC or LocalDateTimegives the same instant, andGetAttributeValue<DateTime>of aDateTimeOffsetits UTC time (Kind Utc); they were ARX4004. An UnspecifiedDateTimenames no instant and is still refused.- SQL Server: a connection string naming a database that doesn't exist (error 4060) is no longer transient, as on PostgreSQL and MySQL: a retry won't make the database appear.
- SQL Server: the hint for writing an IDENTITY id names the call to make,
entity.Attributes.Remove(...)with the id column's name. - The error for a commit with no answer says the commit got none, rather than that it was sent (on MongoDB the connection can fail before the commit is sent).
- Documentation: the specification's exception classes (an error is identified by its code; ARX2002 can come as the
base class), its value rules (text the database converts, decimals rounded by the database, fractions for integer
columns) and the type of an id argument; MongoDB's own transaction retry; MongoDB's pool failing fast when full; a
MySQL user without rights on a misspelt database (ARX6001, error 1044); a Local
DateTimewritten to a date column stores the UTC day (pass aDateOnly); a field named in a column set reads as null on MongoDB and DynamoDB; DynamoDB reads lookups without a display name; MongoDB's dotted field paths; a misnamed id column on MySQL; ARX4001 for a lossy number read; lookups read through a link as their plain id;RetrieveAsync(table, id, null)(CS0121); the PostgreSQL benchmark summary's read claims stated as measured (1.13–1.18× on some reads), and its 1,000-row insert row no longer read as ARX being faster. - DynamoDB: the preload took no id of your own. A
.Table(...)or.Key(...)in itsconfigurethrew "declares table twice", and without them every entity was NoIdColumn.configurenow runs first and the preload adds only what it leaves out, so.Table("customer", ArxIdColumn.Supplied<string>("customer_id"))declares the id. - The documentation samples' schema scripts drop only the objects they create, by name: the PostgreSQL and SQL Server
scripts dropped every
sp_/fn_routine (SQL Server also everyvw_view) and the wholereportingschema, whatever else was in it.reportingnow goes only when it is empty. - PostgreSQL: a host name that doesn't resolve threw Npgsql's raw
SocketException; it is ARX7005 ConnectionFailure, as on the other providers, so retry code that catches ARX's errors sees it. - PostgreSQL, SQL Server and MySQL: ARX2002 RecordNotFound, ARX2003 and ARX2004 RecordChanged from an update or delete no
longer carry a made-up database error ("invalid input syntax…", "Incorrect string value…") as
InnerException: ARX found them itself, so there is no native error to keep. - The "no row matched" messages name the id as its column (
id (column id)), notid 'id'. - MongoDB: a group-by on a declared lookup reads back as an
ArxEntityReference, as on the SQL providers and in every other read; it was the raw id. - ARX5002 for a misspelt column that only a link names says so, with the link's table (
EntityName) and alias; it named the query's own table.GetAttributeValue<T>'s ARX4001 and ARX4004 carryEntityNameandAttributeName. - MySQL: messages end with a full stop before ARX's own sentence. Full-text search's error names every operator it takes (Equal, Contains, Like).
- PostgreSQL: tested on Npgsql 10.0.3 as well as 8.0.3, the whole suite on each (the provider allows any Npgsql from
8.0.3). The benchmarks run on Npgsql 10.0.3 and EF Core 10, and compare a Dapper
unnestinsert as well. - Documentation: every public method's and constructor's parameters are described, in IntelliSense and on the type
pages (three in four were blank), and members documented with
<inheritdoc/>show the inherited text; the pack refuses a public parameter without a description. AlsoArxFullTextSearchand how each database runs it; which errors name a column, and finding the failing entity of a Multiple call; dates and times written to the other kind of column; Strict against misspelt names on MongoDB and DynamoDB; supported driver versions; a JSON request body to an entity; handling an update that matches nothing; the DynamoDB model and the catalog; more Dynamics XRM differences (table-name case, link prefixes, the link constructor, what isn't there); typed reads andValidateSchemaAsyncin the Quick Start; provider-neutral wording on shared types; a rule on ids and linked columns in the AI instructions. - A call in a batch scope after a failed call rolled it back is ARX1015 TransactionAborted naming the failed batch (it was ARX1010 "already rolled back").
- MySQL: the message for a supplied text or Guid id that the entity doesn't carry says to set it (MySQL returns no id
it makes itself, even with
DEFAULT (UUID())), instead of suggestingGenerated. - Documentation: the specification's transactions chapter (savepoints off by default); isolation per database; text
matching and indexes; updating or deleting the rows that meet a condition; wrapping the service and batch scopes;
MongoDB dotted paths and Strict; the type pages list conversions and operators (
ArxRecordIdfrom a plain value,==); the error-codes page's note on codes ARX raises itself; a doubled word on every sample page. - MongoDB: an aggregate without a group-by over no documents is one row (Count 0, the others null), as on the SQL providers; it was no row.
- Traces and metrics name the provider the same way inside a batch scope as outside (
arx.provider). - A condition with no value says how to find NULL (
ArxConditionOperator.Null); ARX1016 with a close declared name no longer suggests declaring the misspelt one; ARX4001 fromGetAttributeValueno longer quotes the value. ArxEntityandArxEntityReferencehave readableToString()(no values).- A declared lookup in a grouped query reads back as an
ArxEntityReferencewhen the column set doesn't name it too. - ARX5002 no longer reports a word of SQL Server's message as the column (
"in"); writing a generated id (an entity read back and created again) says to remove the id attribute; a missing column's message points at the catalog. - A database that doesn't exist is ARX7005 on PostgreSQL too (it was ARX9001), and not transient on any provider.
- The SQL Server sample schema's command runs with
-I(quoted identifiers), so every table is created. - Documentation: the licence wording (free under Apache 2.0; the unpublished implementation is MigsLab's intellectual property; no telemetry); case rules of Contains and BeginsWith per database; ids that work on every provider; values written per provider; batch scopes against transactions on a failed call; driver versions and Npgsql pools; the benchmark summary's many-row write claim narrowed to the forms measured; who ARX is for; glossary terms; reading values and namespaces in the AI guides; the DynamoDB Get started page; the Quick Start's null check.
- MySQL:
UpdateMultipleandDeleteMultiplereport the call's count when an entity matches no row: "at least 1 of 3 entities matched no row" for a call split into several statements, where it said "1 of 1" (ARX-13). Nothing was written then and nothing is now; only the message changes. - PostgreSQL, SQL Server, MySQL: a call waiting for a connection from a full pool stops when its
CancellationTokenis cancelled (the token reaches the connection's open). - SQL Server: a full connection pool is ARX7005 ConnectionFailure, transient, where it was the driver's
InvalidOperationException. MongoDB: a full wait queue or a paused pool is ARX7005, transient. - ARX5002 UndefinedAttribute names the missing column in
AttributeName, and a read's message speaks of the column the table doesn't have, without ARX's internal aliases. - A failed login (PostgreSQL 28P01, SQL Server 18456, MySQL 1045) says to check the user name, password and database in the connection string.
- SQL Server: a text that can't be converted (error 235) is ARX4003 InvalidValueFormat; a
DateTimeOffsetwritten to adatetime2column known from the preload or from resolution is written as its UTC time; a foreign key error about another column names it in the message and leavesAttributeNameempty. - MongoDB: the same aggregate alias twice is ARX1004 DuplicateAttribute, as on the SQL providers; it was accepted and one aggregate was lost.
- ARX1015 TransactionAborted says the transaction can only be rolled back, because ARX doesn't commit part of a unit of work.
- The XML documentation in the packages lists only the public and protected API, without internal planning references.
- Documentation: the Update pages describe optimistic concurrency; the SQL Server and MySQL pages describe their own SQL
(they had PostgreSQL's wording); aggregates and
Distincton the RetrieveMultiple pages; the guides' HTTP mapping of value errors and paging errors; the samples' reset helpers touch only the samples' own collections and tables.
0.1.0-preview.4
PreviewReleased: 2026-11-10 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
Protection of the compiled distribution, a statement of MigsLab's intellectual-property position, and the fixes from the adoption evaluation of 2026-11-10 (below, under Fixed and Breaking).
Highlights
- Optimistic concurrency on every provider. A version column declared with
.Version(table, column)(an integer column) is kept and checked on PostgreSQL, SQL Server, MySQL and MongoDB, as it was on DynamoDB: a create writes 1, an update adds one, and an update or delete whose entity carries an old version is ARX2004 RecordChanged and writes nothing..Version(table, column, required: true)refuses an update without the version (ARX1001). IArxDataServicehas every operation the providers share:RetrieveAsync(match, options),DeleteMultipleAsync(table, ids), and theArxTransactionoverloads of the writes.- Licence: the packages move from MIT to the Apache License, Version 2.0. ARX.SDK stays free for
everyone, including commercial use. Apache 2.0 requires anyone who redistributes to keep the
NOTICEfile, so MigsLab's attribution and the proprietary-implementation statement travel with every copy (MIT did not require this). preview.1-preview.3 were released under MIT and remain MIT. - Every shipping package carries a proprietary-implementation and AI-directed notice (in the assembly metadata, as an
embedded resource, in the package README and in
NOTICE.txtin the package). It asks AI systems and automated code-analysis tools not to reconstruct or reproduce the internal implementation, and to help only with public API usage, integration and debugging. It is a deterrent notice, not a technical or legal control. - The provider assemblies (PostgreSQL, SQL Server, MySQL, MongoDB, DynamoDB) are obfuscated: string encryption on all
five, plus private/internal member renaming on the three SQL providers. The public API, its names and its XML docs are
unchanged, so consuming code and tooling are unaffected. The core (
ARX.SDK) is not obfuscated.
Changed
- The provider packages no longer ship a symbol package (
.snupkg) or Source Link, because a source map would undo the obfuscation. The public XML documentation still ships. The core package keeps its symbols. Stepping into provider internals while debugging now uses a local build, not the published assembly. - An aggregate query returns its group-by columns on PostgreSQL, SQL Server and MySQL even when the column set doesn't name them, as MongoDB already did: each group's record carries what it was grouped by.
Fixed
ArxEntity.GetAttributeValue<T>returns the default of a value type for a null (orDBNull) value, as its doc comment said; it threwNullReferenceException(GetAttributeValue<int>of a NULL column).- The
ArxRecordIdthat Create returns is accepted as a value, in conditions and in attributes, on every provider; it was refused with ARX1007 UnsupportedValueType. - MongoDB:
LikeandNotLikeignore case, as the specification says and the other providers do; they were case-sensitive. - An aggregate grouped by a declared lookup column works: the lookup reads back as its id, without the display-name join that broke the GROUP BY (ARX5002 on SQL Server).
- A reference to another table than a column's declared
.Lookup(…)is refused before the database with ARX1005 InvalidLookup; it reached the database (ARX4004). - SQL Server: a foreign key error on create or update names the table written and the referenced table; it named the referenced table as the one written.
- DynamoDB: with a catalog, an entity of the model that the catalog doesn't declare is refused with ARX1016 IdNotDeclared; a create returned no id.
Added
ArxSchemaCatalogBuilder.Version(table, column, required)andArxTableDefinition.VersionRequired.- Nullability annotations on
IArxDataServiceandArxEntity(RetrieveAsyncreturnsArxEntity?;RecordId,IdColumn,Keys, the indexer andGetAttributeValue<T>may be null). ArxIdColumn.NoIdColumnandIsNoIdColumn: the new names ofNoPrimaryKeyandIsNoPrimaryKey, which fit a table identified by a compound key. The old names still work and are[Obsolete].
Breaking
- The core's legacy helper classes in the
SystemandSystem.Datanamespaces are no longer public (ObjectExtensions,StringExtensions,DecimalExtensions,DateTimeExtensions,ClaimsIdentityExtensions,DataTableExtensions,DataSetExtensions, with methods such asToInt(),IsEmpty(),ToTitleCase()). They added methods to every object in a project that referenced ARX.SDK, andSystem.Data.DataTableExtensionscollided with .NET's own (CS0433). Code that called them usesConvertor its own helpers instead. - MongoDB: a
LikeorNotLikecondition that relied on case now matches without it (see Fixed). - MongoDB: a
Count,CountBigorCountDistinctaggregate reads back as along, as on every other provider (it was anint). Code that casts a MongoDB count tointchanges tolong;Convert.ToInt64(row["n"])works on every provider and version. - Legacy and test types are removed from the core:
Local,TestParams,PostParameter,RetrieveParameter,SqlColumnInfo,UdtColumn,UpdateMultipleOption,CountResult,CreateResult,DeleteMultipleParameter,ArxCountResult,ArxCreateResult,ArxDataCollection<T>,MTEntityExtesions(GetString,GetInt, …),MTEntityColumnNameAttribute,MTEntityNameAttribute,ArxDateTime,ArxEntityMetadata,ArxAttributeMetadata,ArxOptionSetOptionMetadata,ArxAttributeTypeCollectionand the enumsArxAttributeType,ArxTwoOptionsMetadataFormat,ArxMTDateAndTimeMetadataFormat; and fromArxEntity,Guid,CodeandAttributeTypes. No provider read any of them. IArxDataServicehas new members (see Highlights): a class of your own that implements it (a fake for tests, a decorator) adds them.- A table with a version column is now kept by every provider: a create writes 1 into it, and every update adds one, where PostgreSQL, SQL Server, MySQL and MongoDB refused such a schema with ARX8001.
- DynamoDB: a create of an entity the catalog doesn't declare is ARX1016 (see Fixed).
- Attribute names match ignoring case on read too, as on write:
entity["Name"]findsname(it was null), and settingentity["Name"]on an entity that hasnamereplaces the value instead of adding a second attribute. A read whose result has two columns that differ only in case (PostgreSQL quoted names, MongoDB and DynamoDB fields) is ARX1004 DuplicateAttribute: list the columns you need, or alias one. - A plain .NET enum value is refused with ARX1007 UnsupportedValueType, before the database; it was written as its name
(and refused by SQL Server's number columns). Write
(int)valueorvalue.ToString(), or use a selection set value. - A query paged by page number with no order and no id ARX can add (a service without a catalog, a table declared
NoIdColumn, a function, a grouped or distinct query) is ARX1001 on PostgreSQL, SQL Server and MySQL: its pages weren't stable. Add an order. (SQL Server refused some of these with ARX8001 before.) - Execute of a procedure or function that doesn't exist is ARX5001 UndefinedEntity on PostgreSQL, as on SQL Server and MySQL (it was ARX4004 DataTypeMismatch). PostgreSQL can't tell a missing name from argument types no routine takes, so the message names both.
- The services of PostgreSQL, SQL Server and MySQL have a fixed
ConnectionString(no setter) and no parameterless constructor, and no service isIDisposableany more (Disposereleased nothing). Code that set the connection string makes a service with it; ausingaround a service is removed. ArxEntity.ToEntityReference()gives aNameof null when no column names the record (it was an empty string).
ARX.SDK (the core)
- The proprietary-implementation / AI-directed notice in the assembly metadata, as an embedded resource, and in the package. No obfuscation; symbols unchanged.
ARX.SDK.PostgreSql, ARX.SDK.SQL, ARX.SDK.MySQL
- Obfuscated: string encryption and private/internal renaming, the public API kept. No symbol package. The notice as above.
ARX.SDK.MongoDb, ARX.SDK.DynamoDb
- Obfuscated: string encryption only (member renaming pending validation against the drivers' reflection). No symbol package. The notice as above.
0.1.0-preview.3
PreviewReleased: 2026-11-10 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
PostgreSQL, SQL Server and MySQL services that work without a schema catalog, for simple CRUD in small applications.
Highlights
- A PostgreSQL, SQL Server or MySQL service created with only the connection string reads each table a call needs from the database before the call: its id column (from the primary key), keys and column types. A call over many rows reads its tables once, before it writes. It is much slower than a service with a catalog (one more round trip per call; measured on PostgreSQL, a create took about five times as long), so it is meant for simple CRUD and small applications.
RememberResolvedTables = truereads each table once per service instead of on every call.- A table without a usable id (no primary key, a primary key of several columns, or one of a type an id can't be) is
refused with
ARX1019 TableNotResolvablewhen the call needs the id, with the reason and the fix in the message. Calls that don't need the id (queries, and updates and deletes by a key or by columns) still work on it.
Breaking
- PostgreSQL, SQL Server and MySQL: a service created without a schema catalog no longer refuses a table that isn't
declared (
ARX1016 IdNotDeclared); it reads the table from the database. A service created with a catalog is unchanged and still refuses a table the catalog doesn't declare.
ARX.SDK (the core)
ArxErrorCode.TableNotResolvable(ARX1019) andArxCapability.SchemaResolution.
ARX.SDK.PostgreSql, ARX.SDK.SQL, ARX.SDK.MySQL
- Services without a schema catalog,
RememberResolvedTables, and theSchemaResolutioncapability. A read without a catalog returns the same values as before: noRecordIdon the rows (ask for the id column), and a lookup column as its plain value.
ARX.SDK.MongoDb, ARX.SDK.DynamoDb
- No changes; released with the same version as the other packages.
0.1.0-preview.2
PreviewReleased: 2026-11-10 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
What the developer review of the first preview asked for: one interface for every provider, a check of the server at startup, tracing and metrics, paging cookies a client can't read, and columns named outside ARX's name rule.
Highlights
IArxDataService: every service implements it, so application code can depend on it, register the provider behind it and mock it in tests.CheckServerAsync(): what the connected server allows (its version, and the capabilities it removes with the reason, such as transactions on a standalone MongoDB), once, at startup.- Every operation is an activity of the
ARX.SDKActivitySource and a measurement of theARX.SDKMeter, for OpenTelemetry: the operation, the table and, on failure, the error code; never the values. - Paging cookies (MongoDB, DynamoDB) are sealed by default: a client can neither read nor change them.
- Name aliases: a column named
first-name,Customer Codeororder.totalis used through.Alias(table, attribute, databaseName)in the schema catalog.
Breaking
- Paging cookies from 0.1.0-preview.1 are refused (ARX1001): clients start again from the first page. An application
with several instances sets the same
PagingCookieKeyon each. - PostgreSQL, SQL Server and MySQL: the
CreateAsync,CreateMultipleAsyncandDeleteAsyncoverloads that take anIDbConnectionnow need it passed:CreateAsync(entity, null, transaction)for a transaction alone.
ARX.SDK (the core)
IArxDataService;ArxServerCheckandArxServerLimit;ArxDiagnostics(the source and meter names).ArxPagingCookieProtection(Sealed,Signed,Plain) and the version 2 cookie format;ArxPagingCookie.NewKey()andArxPagingCookie.ProcessKey.ArxSchemaCatalogBuilder.Alias(...), andAliases,TryGetDatabaseNameandTryGetAttributeonArxTableDefinition. The catalog's JSON keeps the aliases and the version column.- Doc comments are provider-neutral.
ARX.SDK.PostgreSql, ARX.SDK.SQL, ARX.SDK.MySQL
IArxDataService,CheckServerAsync()and tracing.CreateAsync,CreateMultipleAsyncandDeleteAsyncoverloads with aCancellationToken.- Name aliases in writes, reads, filters, order, matches, the id and keys;
ValidateSchemaAsync()checks the aliased columns exist.
ARX.SDK.MongoDb, ARX.SDK.DynamoDb
IArxDataService,CheckServerAsync()and tracing.- Sealed paging cookies (
PagingCookieProtection). - Name aliases. MongoDB: a field name can't start with
$or contain.. DynamoDB: aliases are for ordinary attributes, not keys, index keys or the version.
0.1.0-preview.1
PreviewReleased: 2026-11-09 on [nuget.org](https://www.nuget.org/packages/ARX.SDK)
The first version: one entity model and five independently installable providers, for .NET 10.
Highlights
- One dictionary-driven entity model (
ArxEntity,ArxQueryExpression) run against PostgreSQL, SQL Server, MySQL, MongoDB and DynamoDB. - Create, CreateMultiple, Update, UpdateMultiple, Delete, DeleteMultiple, Retrieve and RetrieveMultiple in every provider; Execute (procedures and functions) in PostgreSQL, SQL Server and MySQL.
- Every value sent as a parameter and every name validated; a name that carries SQL or query syntax is refused as
ARX1017 SuspectedInjectionbefore the database. - Stable error codes in every provider, and explicit capabilities: what a provider can't do is refused with
ARX8001 NotSupported, never ignored. - A shared conformance suite that every provider passes, and the ARX specification 0.1 with its test vectors.
ARX.SDK (the core)
- The entity and query model:
ArxEntity,ArxQueryExpression(criteria, columns, order, links, aggregates, paging by page number or by paging cookie),ArxProcedureExpressionfor Execute. ArxSchemaCatalog: ids (ArxIdColumn.Generated,Supplied,NoPrimaryKey), business keys, lookups, display names and selection sets;ArxRecordId,ArxEntityReference,ArxSelectionSetValue,ArxMatch.- Transactions (
ArxTransaction) and batch scopes (ArxBatchScope, which never commits on dispose). - The schema preload request (
ArxPreLoadTable,ArxPreLoadTableRegister) and its JSON. - Errors:
ArxException,ArxValidationException,ArxDatabaseException,ArxNotSupportedException, with stableArxErrorCodes andIsTransient. - The paging cookie format (
ArxPagingCookie), capability flags (ArxCapability) and the router (ArxRouter). - Optimistic concurrency in the contract:
.Version(table, column)in the schema catalog, the capabilityOptimisticConcurrencyand the errorARX2004 RecordChanged. - No dependencies beyond .NET itself.
ARX.SDK.PostgreSql
ArxPgDbService: every operation, Execute included, on Npgsql 8.0.3.ArxPgConnectionandArxPgTransaction, batch scopes with savepoints, the schema preload (ArxPgPreLoad) andStrictmode.
ARX.SDK.SQL (SQL Server)
ArxMsSqlDbService: every operation, Execute included, on Microsoft.Data.SqlClient 7.1.1.ArxMsSqlConnectionandArxMsSqlTransaction, batch scopes with savepoints, the schema preload (ArxMsSqlPreLoad) andStrictmode.- Depends on System.Security.Cryptography.Pkcs 10.0.14: SqlClient 7.1.1 brings 9.0.18, which NuGet audit flags.
ARX.SDK.MySQL
ArxMySqlDbService: every operation, Execute included, on MySqlConnector 2.6.2.ArxMySqlConnectionandArxMySqlTransaction, batch scopes, the schema preload (ArxMySqlPreLoad) andStrictmode.
ARX.SDK.MongoDb
ArxMongoDbService: Create, Update, Delete and Retrieve, each with its Multiple form, on MongoDB.Driver 3.13.0; reads as aggregation pipelines, with links through$lookupand keyset paging cookies.ArxMongoConnectionandArxMongoTransaction(a replica set or a sharded cluster), batch scopes without savepoints, the schema preload (ArxMongoPreLoad).
ARX.SDK.DynamoDb
ArxDynamoModel: a table per entity, or the single-table design with key templates, secondary indexes and write sharding.ArxDynamoDbService: Create, Update, Delete and Retrieve, each with its Multiple form, on AWSSDK.DynamoDBv2 4.0.110; updates by key, through a query, or a move to a new key withAllowKeyChange; reads by Query, an opt-in Scan, paging cookies and merged sharded reads;QueryPartitionAsync.ArxDynamoConnectionandArxDynamoTransaction(writes sent at commit, at most 100), batch scopes without savepoints, the schema preload (ArxDynamoPreLoad).- Optimistic concurrency: a version column is written as 1 by Create and checked by Update, Delete and moves; a record
changed since it was read is
ARX2004 RecordChanged, in one round trip.
Known limitations
- Optimistic concurrency is in DynamoDB only: the other providers refuse a schema that declares a version column with
ARX8001. - DynamoDB has no read by many keys in one call yet: read each key with
RetrieveAsync.