# Create with ARX.SDK on MongoDB

## What a create does

| Rule | On MongoDB |
|---|---|
| Where it goes | The entity's name is the collection; MongoDB makes a collection on its first insert |
| The id | The declared id is the document's `_id`, named in your code by its declared column (`account_id`) |
| A generated string id | An ObjectId ARX makes before the insert, returned as its 24-character text |
| A generated Guid id | A version 7 Guid ARX makes (new ids sort by time) |
| A generated number id | Refused (ARX8001): MongoDB has no sequences |
| No id (`NoIdColumn`) | Refused (ARX1001): every MongoDB document has an `_id` |
| A duplicate | ARX3001 UniqueViolation, naming the index; a plain insert never replaces a document |
| Names | ASCII letters, digits and `_`; a `$` or SQL syntax is ARX1017; field names keep their case |
| Without a catalog | Not resolved: a collection has no declared id to read, so a table the catalog (or `entity.IdColumn`) doesn't declare stays ARX1016 IdNotDeclared. The SQL providers resolve it (capability `SchemaResolution`, which MongoDB doesn't declare) |

## Reference

### Value types

| .NET value | Stored as |
|---|---|
| `string`, `char` | string |
| a plain `enum` | refused (ARX1007): write `(int)value` or `value.ToString()`, or use a selection set |
| `int`, `short`, `byte` | 32-bit integer |
| `long`, `uint` | 64-bit integer |
| `decimal` | Decimal128 (exact) |
| `double`, `float` | double (NaN and infinity allowed) |
| `bool` | boolean |
| `Guid` | binary subtype 4 (UUID) |
| `byte[]`, `ArxBytea` | binary |
| `ArxJsonB` | an embedded document or array (its JSON parsed) |
| an array or a list | an array of the values above |
| `ArxSelectionSetValue` | its code, or an array of codes |
| `ArxEntityReference` | the referenced id, as the referenced collection stores it (an ObjectId for a generated string id) |
| `null` | null |

### Dates and times

| .NET value | Stored as |
|---|---|
| `DateTime` (UTC) | a BSON date, to the millisecond: finer ticks are dropped |
| `DateTime` (Local) | converted to UTC |
| `DateTime` (Unspecified) | taken as UTC |
| `DateTimeOffset` | its UTC instant (the offset isn't kept) |
| `DateOnly` | midnight UTC of that day |
| `TimeOnly`, `TimeSpan` | text (`09:30:00.0000000`, `1.02:03:04`) |
| `default(DateTime)`, `default(DateTimeOffset)`, `default(DateOnly)` | refused (ARX1013): they mean "not set" |

## The samples

Every Create sample, in reading order. Each scenario shows the code (verified against MongoDB) and the mongosh query that checks what it did. `service` is the application's `ArxMongoDbService`; values marked "In scope" come from earlier in the sample. What the samples' code refers to beyond the packages (SampleCatalog.cs, `dbContext`) is in the sample application file, arx-mongodb-sample-application.md.

### Create one record and get its id

Basics 1 of Create.

The smallest create: one document, in four scenarios. The usual way first (the id declared in the schema, an ObjectId ARX makes), then the id declared on the entity, then an id you supply, then a Guid id.

Good to know:

The declared id is the document's `_id`. Your code names it by the column you declare (`account_id`); MongoDB stores it as `_id`. A collection that doesn't exist yet is made by MongoDB on the first insert.

Usings:

```csharp
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. Id in the schema

The usual way: the id declared in the schema; ARX makes an ObjectId and returns it as text.

In scope: `connectionString` (string).

```csharp
// Declare each collection's id once, at startup. A generated string id is an ObjectId.
var schema = ArxSchemaCatalog.Create()
    .Table("account", ArxIdColumn.Generated<string>("account_id"))
    .Build();

var service = new ArxMongoDbService(connectionString, schema);

var entity = new ArxEntity("account");
entity["name"] = "Northwind Traders";
entity["account_number"] = "ACC-M001";
entity["employee_count"] = 240;

// CreateAsync returns the id of the new document: the ObjectId's 24 hexadecimal characters.
ArxRecordId id = await service.CreateAsync(entity);

Console.WriteLine($"created account {id.AsString()}");
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M001" })
```

Expect: 1 document, _id an ObjectId, name "Northwind Traders", employee_count 240.

#### 2. Id on the entity

The other way: no schema catalog, the id declared on the entity.

In scope: `connectionString` (string).

```csharp
var service = new ArxMongoDbService(connectionString);

var entity = new ArxEntity("account");
entity.IdColumn = ArxIdColumn.Generated<string>("account_id");
entity["name"] = "Contoso Pharmaceuticals";
entity["account_number"] = "ACC-M002";

await service.CreateAsync(entity);

// The id is also set on the entity.
Console.WriteLine($"created account {entity.RecordId} (read from entity.RecordId)");
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M002" })
```

Expect: 1 document, name "Contoso Pharmaceuticals".

#### 3. An id you supply

An id you supply: a country's code is its id.

In scope: `connectionString` (string).

```csharp
var schema = ArxSchemaCatalog.Create()
    .Table("country", ArxIdColumn.Supplied<string>("code"))
    .Build();

var service = new ArxMongoDbService(connectionString, schema);

var portugal = new ArxEntity("country");
portugal["code"] = "PT";
portugal["name"] = "Portugal";

ArxRecordId id = await service.CreateAsync(portugal);

Console.WriteLine($"created country {id.AsString()}");
```

Check it in the database:

```sql
db.country.find({ _id: "PT" })
```

Expect: 1 document, name "Portugal".

#### 4. A Guid id

A Guid id ARX makes (version 7: new ids sort by time).

In scope: `connectionString` (string).

```csharp
var schema = ArxSchemaCatalog.Create()
    .Table("sales_order", ArxIdColumn.Generated<Guid>("order_id"))
    .Build();

var service = new ArxMongoDbService(connectionString, schema);

var order = new ArxEntity("sales_order");
order["order_number"] = "SO-M001";
order["total"] = 125.50m;

ArxRecordId id = await service.CreateAsync(order);

Console.WriteLine($"created order {id.AsGuid()}");
```

Check it in the database:

```sql
db.sales_order.find({ order_number: "SO-M001" })
```

Expect: 1 document, _id a UUID, total NumberDecimal("125.50").

### Create a record that refers to another

Basics 2 of Create.

A document that refers to another: a contact of an account, in two scenarios. The reference is an `ArxEntityReference` made from the id CreateAsync returned; one to a collection the schema doesn't declare is refused before anything is sent.

Good to know:

MongoDB has no foreign keys: ARX checks the reference against the schema (the collection is declared, the id has its type) and stores it as the referenced id is stored, an ObjectId here.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. A contact of an account

Create the account, then a contact whose account_id refers to it.

In scope: `service` (ArxMongoDbService).

```csharp
var account = new ArxEntity("account");
account["name"] = "Litware, Inc.";
account["account_number"] = "ACC-M201";
ArxRecordId accountId = await service.CreateAsync(account);

var contact = new ArxEntity("contact");
contact["first_name"] = "Ada";
contact["last_name"] = "Lovelace";
contact["email"] = "ada@litware.example";
contact["account_id"] = new ArxEntityReference("account", accountId);

ArxRecordId contactId = await service.CreateAsync(contact);

Console.WriteLine($"created contact {contactId.AsString()} of account {accountId.AsString()}");
```

Check it in the database:

```sql
db.contact.find({ email: "ada@litware.example" })
```

Expect: 1 document, account_id an ObjectId equal to the account's _id.

#### 2. A collection the schema doesn't declare

A reference to a collection the schema doesn't declare: ARX1005, nothing sent.

In scope: `service` (ArxMongoDbService).

```csharp
var contact = new ArxEntity("contact");
contact["email"] = "nobody@litware.example";
contact["account_id"] = new ArxEntityReference("customer", "64b7f0c2a1b2c3d4e5f60718");

try
{
    await service.CreateAsync(contact);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.InvalidLookup)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.contact.find({ email: "nobody@litware.example" })
```

Expect: no documents.

### Catch the errors a create meets

Basics 3 of Create.

The errors a create meets most, in three scenarios: a duplicate (a unique index), a value ARX refuses before sending anything, and an id MongoDB can't make.

Good to know:

Every error is an `ArxException` with a stable code. The message names the index, never the value (MongoDB's own text quotes it). A plain insert never replaces a document.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. A duplicate

A second account with the same number: ARX3001, with the index's name.

In scope: `service` (ArxMongoDbService).

```csharp
var account = new ArxEntity("account");
account["name"] = "Proseware, Inc.";
account["account_number"] = "ACC-M301";
await service.CreateAsync(account);

var again = new ArxEntity("account");
again["name"] = "Proseware copy";
again["account_number"] = "ACC-M301";

try
{
    await service.CreateAsync(again);
}
catch (ArxDatabaseException exception) when (exception.Code == ArxErrorCode.UniqueViolation)
{
    Console.WriteLine(exception.Message);
    Console.WriteLine($"index {exception.ConstraintName}, MongoDB error {exception.NativeCode}");
}
```

Check it in the database:

```sql
db.account.countDocuments({ account_number: "ACC-M301" })
```

Expect: 1.

#### 2. A date that means "not set"

default(DateTime) means "not set": ARX1013 before anything is sent.

In scope: `service` (ArxMongoDbService).

```csharp
var account = new ArxEntity("account");
account["account_number"] = "ACC-M302";
account["created_on"] = default(DateTime);

try
{
    await service.CreateAsync(account);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.InvalidDateTimeValue)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M302" })
```

Expect: no documents.

#### 3. A generated number id

A generated number id: MongoDB has no sequences, so ARX8001; use an ObjectId or a Guid.

In scope: `connectionString` (string).

```csharp
var schema = ArxSchemaCatalog.Create()
    .Table("activity_log", ArxIdColumn.Generated<long>("log_id"))
    .Build();

var service = new ArxMongoDbService(connectionString, schema);

var entry = new ArxEntity("activity_log");
entry["action"] = "signed in";

try
{
    await service.CreateAsync(entry);
}
catch (ArxNotSupportedException exception)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.activity_log.find({ action: "signed in" })
```

Expect: no documents.

### Store every kind of value

Real case 1 of Create.

What each .NET value becomes in MongoDB, in three scenarios: numbers, text and flags; dates and times; and the shapes a document can hold, an embedded document, an array and a picklist with several choices.

Good to know:

- `int` is stored as a 32-bit integer, `long` 64-bit, `decimal` as Decimal128 (exact), `double` as a double.
- A `Guid` is binary subtype 4 (UUID), a `byte[]` plain binary.
- A `DateTime` is a BSON date, in UTC and to the millisecond: finer ticks are dropped. A `DateTimeOffset` keeps its instant, not its offset; a `DateOnly` is midnight UTC; a `TimeOnly` is text.
- `ArxJsonB` is an embedded document or array; a list or an array is an array.

Usings:

```csharp
using ARX.SDK.Models;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. Numbers, text and flags

Numbers keep their width; a decimal stays exact.

In scope: `service` (ArxMongoDbService).

```csharp
var account = new ArxEntity("account");
account["name"] = "Tailspin Toys";
account["account_number"] = "ACC-M401";
account["employee_count"] = 320;
account["annual_revenue"] = 12_500_000.75m;
account["credit_score"] = 0.87;
account["visits"] = 9_000_000_000L;
account["is_partner"] = true;
account["external_id"] = Guid.Parse("8f14e45f-ceea-467f-a8f9-1d2a3b4c5d6e");
account["industry"] = new ArxSelectionSetValue("Industry", (int)Industry.Retail);

var id = await service.CreateAsync(account);

Console.WriteLine($"created account {id.AsString()}");
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M401" })
```

Expect: employee_count 320, annual_revenue NumberDecimal("12500000.75"), visits NumberLong("9000000000"), external_id UUID("8f14e45f-ceea-467f-a8f9-1d2a3b4c5d6e"), industry 1.

#### 2. Dates and times

Dates are UTC and keep milliseconds; a date alone is midnight UTC.

In scope: `service` (ArxMongoDbService).

```csharp
var order = new ArxEntity("sales_order");
order["order_number"] = "SO-M401";
order["ordered_at"] = new DateTime(2026, 11, 9, 14, 30, 15, 250, DateTimeKind.Utc);
order["promised_at"] = new DateTimeOffset(2026, 11, 12, 9, 0, 0, TimeSpan.FromHours(1));
order["ship_on"] = new DateOnly(2026, 11, 10);
order["delivery_window_start"] = new TimeOnly(9, 30);

await service.CreateAsync(order);

Console.WriteLine("created order SO-M401");
```

Check it in the database:

```sql
db.sales_order.find({ order_number: "SO-M401" })
```

Expect: ordered_at ISODate("2026-11-09T14:30:15.250Z"), promised_at ISODate("2026-11-12T08:00:00Z"), ship_on ISODate("2026-11-10T00:00:00Z"), delivery_window_start "09:30:00.0000000".

#### 3. Documents, arrays and picklists

An embedded address, a list of tags, and several preferred channels from a picklist.

In scope: `service` (ArxMongoDbService).

```csharp
var contact = new ArxEntity("contact");
contact["first_name"] = "Grace";
contact["last_name"] = "Hopper";
contact["email"] = "grace@tailspin.example";
contact["address"] = new ArxJsonB { SerializedValue = "{\"street\":\"1 Navy Way\",\"city\":\"Arlington\",\"country\":\"US\"}" };
contact["tags"] = new[] { "speaker", "vip" };
contact["channels"] = ArxSelectionSetValue.Multiple("ContactChannels", "E", "S");

await service.CreateAsync(contact);

Console.WriteLine("created contact grace@tailspin.example");
```

Check it in the database:

```sql
db.contact.find({ email: "grace@tailspin.example" })
```

Expect: address { street: "1 Navy Way", city: "Arlington", country: "US" }, tags ["speaker", "vip"], channels ["E", "S"].

### Catch an injection attempt

Real case 2 of Create.

A form whose fields become attributes, attacked in three ways: a field named like a MongoDB operator, a field named with SQL in it, and a value that looks like an operator.

Good to know:

A name with `$` or SQL syntax is `ARX1017 SuspectedInjection`, refused before anything is sent; alert on it. Values are never parsed: a value that looks like an operator is stored as the text it is.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. An operator as a field name

A posted field named "$where": ARX1017.

In scope: `service` (ArxMongoDbService).

```csharp
var contact = new ArxEntity("contact");
contact["email"] = "mallory@attacker.example";
contact["$where"] = "sleep(5000)";

try
{
    await service.CreateAsync(contact);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.contact.find({ email: "mallory@attacker.example" })
```

Expect: no documents.

#### 2. SQL in a field name

A field name carrying SQL (an attack aimed at a SQL backend): ARX1017 too.

In scope: `service` (ArxMongoDbService).

```csharp
var contact = new ArxEntity("contact");
contact["email"] = "eve@attacker.example";
contact["name'; DROP TABLE contact; --"] = "x";

try
{
    await service.CreateAsync(contact);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.SuspectedInjection)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.contact.find({ email: "eve@attacker.example" })
```

Expect: no documents.

#### 3. An operator as a value

A value that looks like an operator is stored as text, never run.

In scope: `service` (ArxMongoDbService).

```csharp
var contact = new ArxEntity("contact");
contact["email"] = "trent@example.example";
contact["first_name"] = "{ \"$ne\": null }";

await service.CreateAsync(contact);

Console.WriteLine("stored, as text");
```

Check it in the database:

```sql
db.contact.find({ email: "trent@example.example" }, { first_name: 1 })
```

Expect: 1 document, first_name the text "{ \"$ne\": null }".

### Create in an ARX transaction

Advanced case 1 of Create.

Several calls in one ARX transaction, in two scenarios: an account and its contacts committed together, and a transaction a failed call aborts, so nothing of it is stored.

Good to know:

`new ArxMongoConnection(service)` gives ARX transactions on the service's database: no driver types in your code. MongoDB runs transactions on a replica set or a sharded cluster; it has no savepoints, so a call that fails at the database aborts the transaction (later calls are ARX1015).

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. Commit together

An account and two contacts, committed together.

In scope: `service` (ArxMongoDbService).

```csharp
await using var connection = new ArxMongoConnection(service);
await using var transaction = await connection.BeginTransactionAsync();

var account = new ArxEntity("account");
account["name"] = "Wide World Importers";
account["account_number"] = "ACC-M501";
var accountId = await service.CreateAsync(account, transaction);

var first = new ArxEntity("contact");
first["email"] = "lena@wideworld.example";
first["account_id"] = new ArxEntityReference("account", accountId);

var second = new ArxEntity("contact");
second["email"] = "omar@wideworld.example";
second["account_id"] = new ArxEntityReference("account", accountId);

await service.CreateMultipleAsync(new[] { first, second }, transaction);

await transaction.CommitAsync();
Console.WriteLine("committed the account and its two contacts");
```

Check it in the database:

```sql
db.contact.countDocuments({ email: /@wideworld\.example$/ })
```

Expect: 2.

#### 2. A failed call aborts it

The second call repeats an account number: the transaction is aborted and the first account goes too.

In scope: `service` (ArxMongoDbService).

```csharp
await using var connection = new ArxMongoConnection(service);
await using var transaction = await connection.BeginTransactionAsync();

var account = new ArxEntity("account");
account["name"] = "Woodgrove Bank";
account["account_number"] = "ACC-M502";
await service.CreateAsync(account, transaction);

var duplicate = new ArxEntity("account");
duplicate["name"] = "Repeats ACC-M501";
duplicate["account_number"] = "ACC-M501";

try
{
    await service.CreateAsync(duplicate, transaction);
}
catch (ArxDatabaseException exception)
{
    Console.WriteLine(exception.Message);
}

Console.WriteLine($"transaction aborted: {transaction.IsAborted}");
await transaction.RollbackAsync();
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M502" })
```

Expect: no documents.

### A security check around creates: log, block, handle false positives

Advanced case 2 of Create.

A security check around every create (`SecurityGuard`, in the project root), in four scenarios: a normal request passes through; an attack is logged and the user sees a plain message; repeated attacks block the user; and a false positive, a harmless file header flagged as SQL, is triaged and fixed without blocking anyone.

Good to know:

ARX1017 is only raised for a name that is already invalid, so a false positive never stops a call that would otherwise have worked: without the check it would fail with ARX1003. What can be wrong is the label: a harmless name (`E-mail (work)`, `Price (EUR)`, `notes;` pasted into a setting) reported as an attack. Handle it with:

- **The source of the name:** `NameSource.Code` for names from your code or configuration, so an ARX1017 there is logged as a bug and blocks nobody; `NameSource.UserInput` for names built from what a user sent.
- **Strikes, not one event:** a single ARX1017 never blocks anyone.
- **Triage:** the log has ARX's evidence (`contains '('`); if it's harmless, `Unblock` the user and fix what let the name through.
- **The real fix:** map user input to names from a fixed list (headers to attributes, request types to tables), so user input never becomes a name, and neither attacks nor false positives can happen.

Usings:

```csharp
using ARX.SDK.Services;
```

Set up by the sample before its scenarios:

```csharp
var guard = new SecurityGuard(blockAfter: 3);
```

#### 1. A normal request passes through

A normal request goes through the guard untouched.

In scope: `service` (ArxMongoDbService), `guard` (SecurityGuard).

```csharp
var result = await CreateContactAsync(service, guard, "ana", new Dictionary<string, object>
{
    ["first_name"] = "Ana",
    ["last_name"] = "Guard-C1",
    ["email"] = "guard-ana@example.com",
});

Console.WriteLine($"ana: {result.Outcome}, contact {result.Value}");
```

Defined in the sample, used above:

```csharp
private static Task<GuardResult<ArxRecordId>> CreateContactAsync(ArxMongoDbService service, SecurityGuard guard, string user, Dictionary<string, object> posted)
{
    var contact = new ArxEntity("contact");

    foreach (var field in posted)
    {
        contact[field.Key] = field.Value;
    }

    return guard.RunAsync(user, "create contact", NameSource.UserInput, () => service.CreateAsync(contact));
}
```

Check it in the database:

```sql
db.contact.find({ last_name: "Guard-C1" }, { first_name: 1, email: 1 })
```

Expect: 1 document: Ana, guard-ana@example.com.

#### 2. An attack is logged; the user sees a plain message

A field name carrying SQL: ARX refuses it (ARX1017), the guard logs it and the user sees a plain message.

In scope: `service` (ArxMongoDbService), `guard` (SecurityGuard).

```csharp
var result = await CreateContactAsync(service, guard, "mallory", new Dictionary<string, object>
{
    ["first_name"] = "Mallory",
    ["last_name"] = "Guard-C2",
    ["notes\") VALUES ('x'); DROP TABLE contact; --"] = "x",
});

Console.WriteLine($"mallory sees: \"{result.ForTheUser}\"");
var logged = guard.Events[^1];
Console.WriteLine($"security log: {logged.Kind}, {logged.User}, {logged.Action}");
```

Check it in the database:

```sql
db.contact.countDocuments({ last_name: "Guard-C2" })
```

Expect: 0: nothing created.

#### 3. Repeated attacks block the user

Two more attempts make three strikes: the user is blocked, and even a valid request isn't attempted.

In scope: `service` (ArxMongoDbService), `guard` (SecurityGuard).

```csharp
foreach (var probe in new[] { "last_name; DROP TABLE account", "email UNION SELECT 1" })
{
    await CreateContactAsync(service, guard, "mallory", new Dictionary<string, object> { ["first_name"] = "Mallory", [probe] = "x" });
}

var valid = await CreateContactAsync(service, guard, "mallory", new Dictionary<string, object>
{
    ["first_name"] = "Mallory",
    ["last_name"] = "Guard-C3",
});

Console.WriteLine($"mallory blocked: {guard.IsBlocked("mallory")}; a valid request: {valid.Outcome}, \"{valid.ForTheUser}\"");
```

Check it in the database:

```sql
db.contact.countDocuments({ first_name: "Mallory" })
```

Expect: 0: no request from mallory created anything, the last one wasn't even attempted.

#### 4. A false positive: a harmless file header flagged as SQL

A false positive: a supplier file's header "E-mail (work)" is used as an attribute name. The parentheses make ARX flag it as SQL, though it's harmless. One strike doesn't block anyone; triage reads the log, and the fix is the mapping that should have been there: file headers mapped to known attributes, so user input never becomes a name.

In scope: `service` (ArxMongoDbService), `guard` (SecurityGuard).

```csharp
var header = new[] { "first_name", "last_name", "E-mail (work)" };
var row = new object[] { "Sam", "Guard-C4", "guard-sam@example.com" };

var asIs = header.Zip(row).ToDictionary(x => x.First, x => x.Second);
var refused = await CreateContactAsync(service, guard, "sam", asIs);

Console.WriteLine($"sam: {refused.Outcome}; blocked: {guard.IsBlocked("sam")} (one strike blocks nobody)");
Console.WriteLine($"triage reads: {guard.Events[^1].Detail}");

// The fix: known headers mapped to attributes; anything else is ignored (or reported to the user), never a name.
var headerMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    ["first_name"] = "first_name",
    ["last_name"] = "last_name",
    ["E-mail (work)"] = "email",
    ["E-mail"] = "email",
};

var mapped = header.Zip(row).Where(x => headerMap.ContainsKey(x.First)).ToDictionary(x => headerMap[x.First], x => x.Second);
var created = await CreateContactAsync(service, guard, "sam", mapped);

Console.WriteLine($"after the fix: {created.Outcome}, contact {created.Value}");
```

Check it in the database:

```sql
db.contact.find({ last_name: "Guard-C4" }, { first_name: 1, email: 1 })
```

Expect: 1 document: Sam, guard-sam@example.com (created once, after the fix).

### Set up the service from the database's schema

Advanced case 3 of Create.

The service set up from the database instead of by hand, in three scenarios: preload a collection at startup, create through the preloaded service, and Strict, where only the preloaded fields may be written.

Good to know:

`ArxPreLoadTable` reads, once, each collection's id (ObjectId ids are generated strings), its unique indexes (as keys) and the field types of its `$jsonSchema` validator. A value of the wrong type is then refused before anything is sent (ARX4004). MongoDB has no foreign keys: declare lookups in the configure callback.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Services;
```

#### 1. Preload at startup

Preload the lead collection once, at startup.

In scope: `connectionString` (string).

```csharp
var catalog = await new ArxPreLoadTable(new ArxPreLoadTableRegister().PreLoadTable("lead")).LoadAsync(connectionString);

catalog.TryGetTable("lead", out var lead);
Console.WriteLine($"lead: id {lead.IdColumn}, keys {string.Join(", ", lead.Keys)}, fields {string.Join(", ", lead.Columns.Keys)}");

return catalog;
```

Check it in the database:

```sql
db.getCollectionInfos({ name: "lead" })[0].options.validator
```

Expect: the $jsonSchema with email, full_name, score and captured_on.

#### 2. Create through the preloaded service

Create through the preloaded service; a value of the wrong type is refused before MongoDB sees it.

In scope: `connectionString` (string), `catalog` (ArxSchemaCatalog).

```csharp
var service = new ArxMongoDbService(connectionString, catalog);

var lead = new ArxEntity("lead");
lead["email"] = "lead-m701@example.com";
lead["full_name"] = "Kim Abercrombie";
lead["score"] = 72;
lead["captured_on"] = new DateTime(2026, 11, 9, 8, 0, 0, DateTimeKind.Utc);
await service.CreateAsync(lead);
Console.WriteLine("created lead-m701@example.com");

var wrong = new ArxEntity("lead");
wrong["email"] = "lead-m702@example.com";
wrong["score"] = "high";

try
{
    await service.CreateAsync(wrong);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.DataTypeMismatch)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.lead.find({ email: { $in: ["lead-m701@example.com", "lead-m702@example.com"] } })
```

Expect: 1 document, lead-m701@example.com with score 72.

#### 3. Strict

Strict: a field the preload didn't load is refused (ARX1018), so a typo can't add a field.

In scope: `connectionString` (string), `catalog` (ArxSchemaCatalog).

```csharp
var service = new ArxMongoDbService(connectionString, catalog) { Strict = true };

var lead = new ArxEntity("lead");
lead["email"] = "lead-m703@example.com";
lead["scroe"] = 10;

try
{
    await service.CreateAsync(lead);
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.NotPreloaded)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.lead.countDocuments({ email: "lead-m703@example.com" })
```

Expect: 0.

### Set the service up for production

Advanced case 4 of Create.

Set the service up for production, in three scenarios: depend on IArxDataService, check the server at startup, and trace and measure every call.

Good to know:

`IArxDataService` is what every ARX service does, whatever its database: application code can depend on it, register the provider behind it, and mock it in tests (the provider type still has what is its own, such as its connections). `CheckServerAsync` asks the server, once, what it allows: its version, and the capabilities it removes, with the reason (a standalone MongoDB runs no transactions, for one). Every operation is an activity of the `ARX.SDK` ActivitySource and a measurement of the `ARX.SDK` Meter: OpenTelemetry reads them with `AddSource("ARX.SDK")` and `AddMeter("ARX.SDK")`, and so does a plain `ActivityListener`, as here. They carry the operation, the table and, on failure, the ARX code: never the values.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.Services;
using System.Diagnostics;
using System.Diagnostics.Metrics;
```

Set up by the sample before its scenarios:

```csharp
IArxDataService data = service;
```

#### 1. Depend on IArxDataService

An application service that depends on IArxDataService, not on the provider's type.

In scope: `data` (IArxDataService).

```csharp
var onboarding = new Onboarding(data);
await onboarding.AddAsync("Fabrikam Robotics", "ACC-M601");

Console.WriteLine($"added Fabrikam Robotics through IArxDataService ({data.Capabilities.ProviderName})");
```

Defined in the sample, used above:

```csharp
private sealed class Onboarding(IArxDataService data)
{
    public Task<ArxRecordId> AddAsync(string name, string key)
    {
        var record = new ArxEntity("account");
        record["name"] = name;
        record["account_number"] = key;
        return data.CreateAsync(record);
    }
}
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M601" }, { name: 1 })
```

Expect: 1 document, Fabrikam Robotics.

#### 2. Check the server at startup

Ask the server once, at startup, what it allows.

In scope: `data` (IArxDataService).

```csharp
ArxServerCheck check = await data.CheckServerAsync();

Console.WriteLine($"ready: {check.IsReady}; transactions here: {check.Supports(ArxCapability.Transactions)}; limits: {check.Limits.Count}");
```

Check it in the database:

```sql
db.version()
```

Expect: the server's version, the same as check.ServerVersion.

#### 3. Trace and measure every call

Listen to ARX's activities and metrics: one per call, with its table, and its code when it fails.

In scope: `data` (IArxDataService).

```csharp
var calls = new List<string>();
using var tracing = new ActivityListener
{
    ShouldListenTo = source => source.Name == ArxDiagnostics.SourceName,
    Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
    ActivityStopped = activity => calls.Add(activity.DisplayName + (activity.GetTagItem("arx.error.code") is { } code ? $" ({code})" : string.Empty))
};
ActivitySource.AddActivityListener(tracing);

var durations = 0;
using var metrics = new MeterListener
{
    InstrumentPublished = (instrument, listener) =>
    {
        if (instrument.Meter.Name == ArxDiagnostics.SourceName && instrument.Name == "arx.operation.duration")
        {
            listener.EnableMeasurementEvents(instrument);
        }
    }
};
metrics.SetMeasurementEventCallback<double>((_, _, _, _) => Interlocked.Increment(ref durations));
metrics.Start();

await new Onboarding(data).AddAsync("Litware Labs", "ACC-M602");

try
{
    var nobody = new ArxEntity("account") { RecordId = ArxRecordId.Of("account_id", "000000000000000000000000") };
    nobody["name"] = "Nobody";
    await data.UpdateAsync(nobody);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.RecordNotFound)
{
    Console.WriteLine(exception.Message);
}

Console.WriteLine($"traced: {string.Join("; ", calls)}; durations recorded: {durations}");
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M602" }, { name: 1 })
```

Expect: 1 document, Litware Labs (the update found nothing to change).

### Use fields named outside the name rule, through aliases

Advanced case 5 of Create.

Use documents whose fields are named outside ARX's name rule, in three scenarios: declare an alias for each such field and create documents through them, read and query by the aliases, and see what is still refused: the field's own name in a call, and a field name MongoDB would read as a path.

Good to know:

ARX accepts only names of ASCII letters, digits and underscores. Documents written by other code may have fields such as `first-name` or `Order Total`: declare an alias for each in the schema catalog, `.Alias(table, attribute, databaseName)`. Code uses the attribute; ARX writes, filters and sorts on the field, and reads it back as the attribute. The declared id is the document's `_id`, as always.

Only declared names pass, and a field name may not start with `$` nor contain `.`: MongoDB reads those as an operator or a path.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.MongoDb.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
```

Set up by the sample before its scenarios:

```csharp
var schema = ArxSchemaCatalog.Create()
    .Table("legacy_customer", ArxIdColumn.Supplied<string>("customer_code"))
    .Alias("legacy_customer", "first_name", "first-name")
    .Alias("legacy_customer", "last_name", "last-name")
    .Alias("legacy_customer", "order_total", "Order Total")
    .Build();
var service = new ArxMongoDbService(connectionString, schema);
```

#### 1. Create through the aliases

Create two customers: each attribute is written to the field its alias names.

In scope: `service` (ArxMongoDbService).

```csharp
var ana = new ArxEntity("legacy_customer");
ana["customer_code"] = "C-1001";
ana["first_name"] = "Ana";
ana["last_name"] = "Silva";
ana["order_total"] = 120.50m;

var id = await service.CreateAsync(ana);

var ben = new ArxEntity("legacy_customer");
ben["customer_code"] = "C-1002";
ben["first_name"] = "Ben";
ben["order_total"] = 75.00m;

await service.CreateAsync(ben);

Console.WriteLine($"created {id} and C-1002");
```

Check it in the database:

```sql
db.legacy_customer.find({ _id: { $in: ["C-1001", "C-1002"] } }, { "first-name": 1, "Order Total": 1 })
```

Expect: 2 documents: Ana with 120.50, Ben with 75.00.

#### 2. Read and query by the aliases

Read a document by id and query by the aliases: every field comes back under its alias.

In scope: `service` (ArxMongoDbService).

```csharp
var ana = await service.RetrieveAsync("legacy_customer", ArxRecordId.Of("customer_code", "C-1001")) ?? throw new InvalidOperationException("The legacy_customer record isn't there.");

Console.WriteLine($"{ana["customer_code"]}: {ana["first_name"]} {ana["last_name"]}, total {ana["order_total"]}");

var query = new ArxQueryExpression("legacy_customer");
query.ColumnSet.AddColumns("first_name", "order_total");
query.Criteria.AddCondition("order_total", ArxConditionOperator.GreaterThan, 50m);
query.AddOrder("first_name", ArxOrderType.Descending);

var customers = await service.RetrieveMultipleAsync(query);

foreach (var customer in customers.Entities)
{
    Console.WriteLine($"{customer["first_name"]}: {customer["order_total"]}");
}
```

Check it in the database:

```sql
db.legacy_customer.find({ "Order Total": { $gt: 50 } }, { "first-name": 1, "Order Total": 1 }).sort({ "first-name": -1 })
```

Expect: Ben, then Ana.

#### 3. What is still refused

The field's own name in a call, and an alias to a field name MongoDB reads as a path, are refused.

In scope: `connectionString` (string), `service` (ArxMongoDbService).

```csharp
var change = new ArxEntity("legacy_customer");
change["customer_code"] = "C-1002";
change["first-name"] = "Benjamin";

try
{
    await service.UpdateAsync(change);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.InvalidIdentifier)
{
    Console.WriteLine(exception.Message);
}

var dotted = new ArxMongoDbService(connectionString, ArxSchemaCatalog.Create()
    .Table("legacy_customer", ArxIdColumn.Supplied<string>("customer_code"))
    .Alias("legacy_customer", "order_total", "order.total")
    .Build());

var carl = new ArxEntity("legacy_customer");
carl["customer_code"] = "C-1003";
carl["order_total"] = 10m;

try
{
    await dotted.CreateAsync(carl);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.InvalidIdentifier)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.legacy_customer.find({ _id: { $in: ["C-1002", "C-1003"] } }, { "first-name": 1 })
```

Expect: 1 document, C-1002 with Ben (the update and the create were refused).

### Log every call through ILogger

Advanced case 6 of Create.

Log every ARX call through ILogger, in three scenarios: one line per call with its table and how long it took, a failed call with its ARX code (and the exception logged where it is caught), and only the calls worth keeping in production.

Good to know:

ARX has no logger of its own and takes no logging package: every call is an activity of the `ARX.SDK` ActivitySource. `ArxCallLog`, below, listens to those activities and writes one log line per call: copy it into your application and give it the `ILogger` you already have (`ILogger<T>` from dependency injection), once, at startup. Here a small logger prints to the console, so the sample's output stays in order.

A line names the operation and the table, never the values sent: what ARX logs is safe to keep. An exception's message is written the same way, so it can be logged where it is caught.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.Query;
using ARX.SDK.Services;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
```

Set up by the sample before its scenarios:

```csharp
ILogger logger = new ConsoleLineLogger();
```

#### 1. One line per call

Create, read and query an account: one Information line each, with the table and the time it took.

In scope: `data` (IArxDataService), `logger` (ILogger).

```csharp
using var callLog = new ArxCallLog(logger);

var account = new ArxEntity("account");
account["name"] = "Tailspin Toys";
account["account_number"] = "ACC-M611";

ArxRecordId id = await data.CreateAsync(account);
await data.RetrieveAsync("account", id);

var query = new ArxQueryExpression("account");
query.Criteria.AddCondition("account_number", ArxConditionOperator.Equal, "ACC-M611");
await data.RetrieveMultipleAsync(query);
```

Defined in the sample, used above:

```csharp
private sealed class ArxCallLog : IDisposable
{
    private readonly ActivityListener _listener;

    public ArxCallLog(ILogger logger, bool logSuccessfulCalls = true)
    {
        _listener = new ActivityListener
        {
            ShouldListenTo = source => source.Name == ArxDiagnostics.SourceName,
            Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
            ActivityStopped = activity => Write(logger, activity, logSuccessfulCalls)
        };

        ActivitySource.AddActivityListener(_listener);
    }

    public void Dispose() => _listener.Dispose();

    private static void Write(ILogger logger, Activity activity, bool logSuccessfulCalls)
    {
        var milliseconds = Math.Round(activity.Duration.TotalMilliseconds, 1);

        if (activity.Status == ActivityStatusCode.Error)
        {
            logger.LogWarning("ARX {Call} failed with {Code} after {Milliseconds} ms", activity.DisplayName, activity.GetTagItem("arx.error.code"), milliseconds);
        }
        else if (logSuccessfulCalls)
        {
            logger.LogInformation("ARX {Call} took {Milliseconds} ms", activity.DisplayName, milliseconds);
        }
    }
}
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M611" }, { name: 1 })
```

Expect: 1 document, Tailspin Toys.

#### 2. A failed call, with its code

A call that fails: the listener logs a Warning with its ARX code; the code that caught it logs the exception.

In scope: `data` (IArxDataService), `logger` (ILogger).

```csharp
using var callLog = new ArxCallLog(logger);

var nobody = new ArxEntity("account") { RecordId = ArxRecordId.Of("account_id", "000000000000000000000000") };
nobody["name"] = "Nobody";

try
{
    await data.UpdateAsync(nobody);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.RecordNotFound)
{
    logger.LogError(exception, "Renaming account {AccountId} failed: {Code}, transient: {IsTransient}",
        "000000000000000000000000", ArxException.FormatCode(exception.Code), exception.IsTransient);
}
```

Check it in the database:

```sql
db.account.countDocuments({ name: "Nobody" })
```

Expect: 0 (the update found no account to change).

#### 3. Only what production needs

In production, log failures only: a create that works writes nothing; an update that fails writes its line.

In scope: `data` (IArxDataService), `logger` (ILogger).

```csharp
using var callLog = new ArxCallLog(logger, logSuccessfulCalls: false);

var account = new ArxEntity("account");
account["name"] = "Wingtip Toys";
account["account_number"] = "ACC-M612";
await data.CreateAsync(account);

Console.WriteLine("created Wingtip Toys: nothing logged");

var gone = new ArxEntity("account") { RecordId = ArxRecordId.Of("account_id", "000000000000000000000000") };
gone["name"] = "Gone";

try
{
    await data.UpdateAsync(gone);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.RecordNotFound)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
db.account.find({ account_number: "ACC-M612" }, { name: 1 })
```

Expect: 1 document, Wingtip Toys.

