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 candidate

Released: 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 an ArxTransaction) on every service, IArxDataService and IArxBatchScope: 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.Name is string?; ArxPagingInfo.Next returns ArxPagingInfo?.
  • A reflection test of the core and a conformance test of each provider pin the public surface.

0.1.0-preview.5

Preview

Released: 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 (State RolledBack, a transient ARX7005), though the database may have kept the work, so retry code could save it twice. Such a commit is now OutcomeUnknown (on ArxTransaction and 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 holding 0001 could match another read's key (table acct with column 0001name, and table acct0001name). 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") with AddGroupByColumn("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 a DateOnly column, and every later read of the item failed with ARX5003. Text is never parsed as a date or time; a Guid column takes a Guid or text that is one.
  • RunBatchAsync reports 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, or Savepoints = 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 DateTimeOffset is written as its UTC instant whatever ARX knows about the column, so a datetime2 column 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 declared decimal column.
  • PostgreSQL: a numeric holding NaN, Infinity or -Infinity made every read of its row fail with a raw InvalidCastException (a .NET decimal can't hold them). They now read back as a double.
  • MongoDB and DynamoDB: field names are case-sensitive there, so an update with entity["name"] on a document holding Name added 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 RecordId set by the caller (XRM code sets entity.Id before 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 IsTransient true, 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) with IsTransient false and a message that says so. A failure before anything was sent stays transient.

Breaking

  • Create refuses a RecordId your 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 .Lookup to a table declared NoIdColumn is 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.
  • ArxTransactionState and ArxBatchState have a new value, OutcomeUnknown: a switch over them adds it.
  • IArxDataService and IArxBatchScope have UpdateMultipleAsync(values, filter), DeleteMultipleAsync(table, filter) and CreateMultipleAsync(entities, ArxCreateMultipleOptions) (each also with an ArxTransaction): a class of your own that implements them adds them (a decorator built with DispatchProxy needs nothing).
  • IArxDataService has BeginTransactionAsync: a class of your own that implements the interface adds it (a decorator built with DispatchProxy needs nothing).
  • A record read from a table declared ArxIdColumn.NoIdColumn has RecordId ArxRecordId.None, as Create returns, not null (PostgreSQL, SQL Server, MySQL, DynamoDB; MongoDB refuses such tables). Test RecordId.HasValue, not null.
  • SQL Server: a DateTimeOffset is 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.Id and .Guid are obsolete: Id read 0 for a Guid or text id. Use RecordId. Setting Guid now sets the id.
  • ArxRecordId equality ignores the column when one side has none ("abc" equals the id Create returned for "abc"), and == compares values.
  • SQL Server: a DateTimeOffset written to a datetimeoffset column 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 as T is ARX4004 DataTypeMismatch (a number that doesn't fit, ARX4001), an ArxValidationException, where it threw InvalidCastException.
  • ArxJoinOperator and ArxDateTimeConditionOptionType moved from ARX.SDK.Metadata to ARX.SDK.Query, with the other query types: replace using ARX.SDK.Metadata; with using ARX.SDK.Query;.
  • IArxDataService has four more members (Retrieve, RetrieveMultiple and Execute in an ArxTransaction); 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 RunBatchAsync throws 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 (capability ArxCapability.Savepoints); MongoDB and DynamoDB refuse true (ARX8001). ArxBatchOptions.Savepoints is a bool? (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, ArxColumnSet and ArxEntityCollection no longer implement IDisposable: they hold nothing to release. Remove a using around 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, DeclaresKey and DeclaresColumn: 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/deleteMany in 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 (unless ArxFilterWriteOptions.AllRows) or a condition on a link is ARX1001. A version column goes up by one on every changed row. Capability WriteByFilter, declared by all five.
  • CreateMultiple without ids: CreateMultipleAsync(entities, new ArxCreateMultipleOptions { ReturnIds = false }) reads no ids back and returns ArxRecordId.None for each, all or nothing as before. PostgreSQL loads a group of 5,000 rows or more of one shape with binary COPY, SQL Server one of 1,000 or more with SqlBulkCopy (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, RetrieveMultipleAsync and ExecuteAsync in an ArxTransaction (DynamoDB checks the transaction and reads as usual: its writes are sent at commit).
  • The providers' RetrieveAsync is annotated Task<ArxEntity?>, so a caller with nullable enabled is warned before using a record that may not be there.
  • ArxRecordId converts from a long, int, Guid or string: 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 RetrieveAsync by 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); FormattedValues is never filled by a provider; a literal null for the options of a write by a filter is CS0121; the AI guides on RecordId (a class: pass it, not .Value); the specification on errors that aren't transient because an outcome is unknown.
  • GetAttributeValue<DateOnly> of a DateTime at midnight gives its date (MongoDB stores a DateOnly that 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_reporting when 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 no RecordId; letter case on MongoDB and DynamoDB in the switching guide; the portable paging loop with Next; the Create rule for RecordId in the XRM guide; the MySQL README's description of its script.
  • GetAttributeValue<DateTimeOffset> of a UTC or Local DateTime gives the same instant, and GetAttributeValue<DateTime> of a DateTimeOffset its UTC time (Kind Utc); they were ARX4004. An Unspecified DateTime names 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 DateTime written to a date column stores the UTC day (pass a DateOnly); 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 its configure threw "declares table twice", and without them every entity was NoIdColumn. configure now 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 every vw_ view) and the whole reporting schema, whatever else was in it. reporting now 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)), not id '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 carry EntityName and AttributeName.
  • 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 unnest insert 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. Also ArxFullTextSearch and 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 and ValidateSchemaAsync in 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 suggesting Generated.
  • 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 (ArxRecordId from 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 from GetAttributeValue no longer quotes the value.
  • ArxEntity and ArxEntityReference have readable ToString() (no values).
  • A declared lookup in a grouped query reads back as an ArxEntityReference when 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: UpdateMultiple and DeleteMultiple report 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 CancellationToken is 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 DateTimeOffset written to a datetime2 column 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 leaves AttributeName empty.
  • 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 Distinct on 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

Preview

Released: 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).
  • IArxDataService has every operation the providers share: RetrieveAsync(match, options), DeleteMultipleAsync(table, ids), and the ArxTransaction overloads 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 NOTICE file, 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.txt in 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 (or DBNull) value, as its doc comment said; it threw NullReferenceException (GetAttributeValue<int> of a NULL column).
  • The ArxRecordId that Create returns is accepted as a value, in conditions and in attributes, on every provider; it was refused with ARX1007 UnsupportedValueType.
  • MongoDB: Like and NotLike ignore 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) and ArxTableDefinition.VersionRequired.
  • Nullability annotations on IArxDataService and ArxEntity (RetrieveAsync returns ArxEntity?; RecordId, IdColumn, Keys, the indexer and GetAttributeValue<T> may be null).
  • ArxIdColumn.NoIdColumn and IsNoIdColumn: the new names of NoPrimaryKey and IsNoPrimaryKey, 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 System and System.Data namespaces are no longer public (ObjectExtensions, StringExtensions, DecimalExtensions, DateTimeExtensions, ClaimsIdentityExtensions, DataTableExtensions, DataSetExtensions, with methods such as ToInt(), IsEmpty(), ToTitleCase()). They added methods to every object in a project that referenced ARX.SDK, and System.Data.DataTableExtensions collided with .NET's own (CS0433). Code that called them uses Convert or its own helpers instead.
  • MongoDB: a Like or NotLike condition that relied on case now matches without it (see Fixed).
  • MongoDB: a Count, CountBig or CountDistinct aggregate reads back as a long, as on every other provider (it was an int). Code that casts a MongoDB count to int changes to long; 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, ArxAttributeTypeCollection and the enums ArxAttributeType, ArxTwoOptionsMetadataFormat, ArxMTDateAndTimeMetadataFormat; and from ArxEntity, Guid, Code and AttributeTypes. No provider read any of them.
  • IArxDataService has 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"] finds name (it was null), and setting entity["Name"] on an entity that has name replaces 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)value or value.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 is IDisposable any more (Dispose released nothing). Code that set the connection string makes a service with it; a using around a service is removed.
  • ArxEntity.ToEntityReference() gives a Name of 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

Preview

Released: 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 = true reads 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 TableNotResolvable when 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) and ArxCapability.SchemaResolution.

ARX.SDK.PostgreSql, ARX.SDK.SQL, ARX.SDK.MySQL

  • Services without a schema catalog, RememberResolvedTables, and the SchemaResolution capability. A read without a catalog returns the same values as before: no RecordId on 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

Preview

Released: 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.SDK ActivitySource and a measurement of the ARX.SDK Meter, 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 Code or order.total is 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 PagingCookieKey on each.
  • PostgreSQL, SQL Server and MySQL: the CreateAsync, CreateMultipleAsync and DeleteAsync overloads that take an IDbConnection now need it passed: CreateAsync(entity, null, transaction) for a transaction alone.

ARX.SDK (the core)

  • IArxDataService; ArxServerCheck and ArxServerLimit; ArxDiagnostics (the source and meter names).
  • ArxPagingCookieProtection (Sealed, Signed, Plain) and the version 2 cookie format; ArxPagingCookie.NewKey() and ArxPagingCookie.ProcessKey.
  • ArxSchemaCatalogBuilder.Alias(...), and Aliases, TryGetDatabaseName and TryGetAttribute on ArxTableDefinition. 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, CreateMultipleAsync and DeleteAsync overloads with a CancellationToken.
  • 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

Preview

Released: 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 SuspectedInjection before 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), ArxProcedureExpression for 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 stable ArxErrorCodes and IsTransient.
  • 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 capability OptimisticConcurrency and the error ARX2004 RecordChanged.
  • No dependencies beyond .NET itself.

ARX.SDK.PostgreSql

  • ArxPgDbService: every operation, Execute included, on Npgsql 8.0.3.
  • ArxPgConnection and ArxPgTransaction, batch scopes with savepoints, the schema preload (ArxPgPreLoad) and Strict mode.

ARX.SDK.SQL (SQL Server)

  • ArxMsSqlDbService: every operation, Execute included, on Microsoft.Data.SqlClient 7.1.1.
  • ArxMsSqlConnection and ArxMsSqlTransaction, batch scopes with savepoints, the schema preload (ArxMsSqlPreLoad) and Strict mode.
  • 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.
  • ArxMySqlConnection and ArxMySqlTransaction, batch scopes, the schema preload (ArxMySqlPreLoad) and Strict mode.

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 $lookup and keyset paging cookies.
  • ArxMongoConnection and ArxMongoTransaction (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 with AllowKeyChange; reads by Query, an opt-in Scan, paging cookies and merged sharded reads; QueryPartitionAsync.
  • ArxDynamoConnection and ArxDynamoTransaction (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.