Guides
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.
RetrieveAsyncreturnsnullandDeleteAsyncreturnsfalsewhere the Dynamics SDK throws;UpdateAsyncthrows ARX2002, as it does. - Attribute names. They are your column names, and ARX matches them ignoring case:
entity["Name"]reads and writes the columnname, 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'saccount: it's ARX1016, with a "Did you mean 'account'?" hint. Write table names as the catalog declares them. - Namespaces.
ArxEntity,ArxEntityReference,ArxColumnSetandArxLinkEntityare inARX.SDK.Services.ArxQueryExpression, conditions, filters, orders andArxJoinOperatorare inARX.SDK.Query, and the selection-set helpers inARX.SDK.Extensions. - What isn't there. There's no
Upsert, no N:NAssociateorDisassociate(write the intersect table's rows), no FetchXML, and no relative date operators (LastXDays,ThisMonthand the like): compute the dates and useGreaterEqualandLessThan. - 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), andDeleteMultipleAsync("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.