# Coming from Dynamics XRM

ARX.SDK's model is inspired by the Dynamics 365 / Dynamics CRM SDK, so most of what you write there has a direct
counterpart here. ARX is a data access library over your own database, not the CRM platform: this page maps the concepts
and lists where the same name behaves differently.

## The concepts, side by side

| Dynamics SDK | ARX.SDK | Same, or different |
|---|---|---|
| `Entity` | `ArxEntity("account")`: a table name and its attributes | Same idea; the name is your table's. `entity.Id` is `entity.RecordId`, an `ArxRecordId` (set by Create and by reads). Unlike XRM, Create takes the id from the id attribute: to give a new record its id, set `entity["accountid"]` (the declared id column), not `RecordId`. A `RecordId` your code sets before a Create is ARX1001 unless the attribute holds the same id (from 0.1.0-preview.5; it was ignored and the database made a new id) |
| `entity["name"]`, `Contains`, `GetAttributeValue<T>` | The same three | The indexer returns `null` for a missing attribute instead of throwing; `GetAttributeValue<T>` converts numbers when nothing is lost (a Count, always a `long`, read as `int`) and reads a lookup as its id; a number that doesn't fit is ARX4001 (1.5 read as `int`), any other mismatch is ARX4004, an `ArxValidationException`, not an `InvalidCastException`. Of a NULL column it is `default(T)` (0 for `int`), as in XRM: ask for `int?` to tell NULL from 0. Unlike XRM, a column that was read as NULL is present: `Contains` is true and its value is null (XRM leaves it out). `entity.Attributes["x"]` is the dictionary and throws `KeyNotFoundException` for a missing attribute, as XRM's indexer does |
| `EntityReference` | `ArxEntityReference(table, id, name)` | Same. A lookup column is declared in the schema catalog (`.Lookup("contact", "account_id", "account")`) and reads back as an `ArxEntityReference`, with the name `.DisplayName("account", "name")` gives. Its id is `reference.RecordId`: the obsolete `Id` property holds numbers only, so it reads 0 for a Guid or text id |
| `Guid` ids everywhere | The id column you declare, of any type (`ArxIdColumn.Generated<long>("id")`, `Supplied<string>("code")`, a `Guid`) | Different: `CreateAsync` returns an `ArxRecordId`, not a `Guid` |
| `IOrganizationService` | `IArxDataService`, implemented by each provider's service (`ArxMsSqlDbService`, …) | Same operations, asynchronous: `CreateAsync`, `RetrieveAsync`, `RetrieveMultipleAsync`, `UpdateAsync`, `DeleteAsync` |
| `QueryExpression` | `ArxQueryExpression` | Same shape: `ColumnSet`, `Criteria`, `Orders`, `LinkEntities`, `PageInfo` |
| `ColumnSet(true)`, `new ColumnSet()`, `ColumnSet(false)` | `ArxColumnSet.All`, and an empty `new ArxColumnSet()`, read every column; there is no bool constructor (from 1.0.0-rc.1) | Different: an empty column set means all columns, where XRM's (and `ColumnSet(false)`) means the id only. To read only the id, name it: `new ArxColumnSet("accountid")` |
| `FilterExpression`, `LogicalOperator.Or` | `query.Criteria.AddFilter(ArxLogicalOperator.Or)` | Same |
| `ConditionExpression`, `ConditionOperator` | `AddCondition("name", ArxConditionOperator.Equal, "Contoso")` | Same; a value is always a parameter |
| `LinkEntity`, `EntityAlias`, `LinkCriteria`, `JoinOperator` | `query.AddLink("contact", "id", "account_id", ArxJoinOperator.Left)`, with the same properties (`ArxJoinOperator` is in `ARX.SDK.Query`, with the other query types). The constructor takes four arguments, `new ArxLinkEntity(linkToTable, linkFromColumn, linkToColumn, join)`: the table it links from is the one it's added to, so there's no `linkFromEntityName` | Different on read: a linked column is `row["alias.column"]`, the plain value, not an `AliasedValue`. That includes a lookup: through a link it is its raw id (a `long`, say), not an `ArxEntityReference`, so read it with `GetAttributeValue<long>`, not a cast. Without an alias the prefix is the linked table's name (`row["account.name"]`), not XRM's numbered `account1.` |
| `OptionSetValue` | A selection set: the raw value is stored; labels come from the catalog (`.Add("Status", …)`) and `entity.GetSelectionSet(…)` (`ARX.SDK.Extensions`) | Different: a set isn't bound to a column, so name the set when you write (`new ArxSelectionSetValue("Status", 2)`) and when you read; ARX checks the value against the set you name, not against the column. A plain `int` written to the column isn't checked at all |
| `FormattedValues` | None: `ArxEntity` has no `FormattedValues` (removed in 1.0.0-rc.1; no provider ever filled it) | Missing. For an option's label, use `entity.GetSelectionSetDisplayText("industrycode", "Industry", catalog)` (`ARX.SDK.Extensions`); for a lookup's name, the reference's `Name` |
| `Money` | `decimal` | Same value, no wrapper |
| `PagingCookie`, `MoreRecords` | `PageInfo.PagingCookie`, `HasNextPage` | Cookies on MongoDB and DynamoDB; the SQL providers page by number and refuse a cookie (ARX8001) |
| `ExecuteMultipleRequest` | `CreateMultipleAsync` / `UpdateMultipleAsync` / `DeleteMultipleAsync`, or a batch scope (`RunBatchAsync`) | Different. The Multiple calls are all or nothing. A batch scope runs many calls in one transaction: with savepoints (`new ArxBatchOptions { Savepoints = true }`, on PostgreSQL, SQL Server and MySQL) a call that fails is undone alone, you catch its error and the others still commit, like `ContinueOnError = true` inside one transaction; without savepoints (the default) the first failure rolls the scope back and `RunBatchAsync` throws ARX1015. A failed Multiple call doesn't say which entity failed: the database checks the statement as a whole, and there's no `RequestIndex`. To find the entity, send one call per entity in a scope with savepoints, where the error's `BatchNumber` is the number of the failed call |
| `Execute(OrganizationRequest)` | `ExecuteAsync(ArxProcedureExpression)` | Different: it calls one of your database's procedures or functions |
| `FaultException<OrganizationServiceFault>` | `ArxException` with a stable `Code` (`ARX2002 RecordNotFound`, …) | Catch by code (Errors in practice) |

## Where the similar names behave differently

- **A record that isn't there.** `RetrieveAsync` returns `null` and `DeleteAsync` returns `false` where the Dynamics SDK
  throws; `UpdateAsync` throws ARX2002, as it does.
- **Attribute names.** They are your column names, and ARX matches them ignoring case: `entity["Name"]` reads and
  writes the column `name`, keeping the spelling it was first given. XRM's logical names are lowercase and matched
  exactly, so code that already uses lowercase names behaves the same.
- **Table names are matched exactly.** `new ArxEntity("Account")` isn't the catalog's `account`: it's ARX1016, with a
  "Did you mean 'account'?" hint. Write table names as the catalog declares them.
- **Namespaces.** `ArxEntity`, `ArxEntityReference`, `ArxColumnSet` and `ArxLinkEntity` are in `ARX.SDK.Services`.
  `ArxQueryExpression`, conditions, filters, orders and `ArxJoinOperator` are in `ARX.SDK.Query`, and the selection-set
  helpers in `ARX.SDK.Extensions`.
- **What isn't there.** There's no `Upsert`, no N:N `Associate` or `Disassociate` (write the intersect table's rows), no
  FetchXML, and no relative date operators (`LastXDays`, `ThisMonth` and the like): compute the dates and use
  `GreaterEqual` and `LessThan`.
- **Bulk update and delete.** Where XRM has a bulk-delete job and no update by a query, ARX changes or deletes every row a
  filter matches in one call: `UpdateMultipleAsync(values, filter)` with an entity that carries only the values (no
  id: ARX1001), and `DeleteMultipleAsync("account", filter)`. They run at once, all or nothing, and return the count.
- **Keys.** `.Key("account", "account_number")` declares a business key ARX can find rows by
  (`ArxMatch.Key("account_number")`); the database's unique constraint is what enforces it.
- **No platform.** No plugins, workflows, security roles, audit or business rules: put that logic in your application,
  or in the database.

## Where to start

Installation sets up a service; each provider's operation pages have samples for every operation in the same order:
Create, CreateMultiple, Update, UpdateMultiple, Delete, DeleteMultiple, Retrieve, RetrieveMultiple, Execute.
