# Create with ARX.SDK on Amazon DynamoDB

## What a create does

| Rule | On DynamoDB |
|---|---|
| Where it goes | The model says: a table of the entity's own (`.Table("product", partitionKey: "sku")`), or a single table shared by several entities, whose keys are templates (`PK = CUSTOMER#{customer_id}`) |
| The keys | ARX writes the table's key attributes, the index keys and `entity_type` from the model; the entity gives the values the templates name |
| An index's attributes | A create may leave out an attribute an index key is made of (a GSI is sparse): the item is written without that index key and isn't in the index, so a query through the index doesn't find it. An update that writes some of an index key's attributes must give the rest (ARX1001), because ARX writes the whole index key again; give none to leave the index key as it is |
| The id | Supplied by the application: DynamoDB generates no ids, so a generated id is ARX8001 (give a `Guid.CreateVersion7()` or your own) |
| A duplicate | ARX3001 UniqueViolation: every create is a conditional put (`attribute_not_exists`) and never overwrites |
| Names | ASCII letters, digits and `_`; `#`, `:` (expression syntax) or SQL syntax is ARX1017; names reach DynamoDB only as placeholders |
| Key values | Escaped in templates (`#` is stored as `%23`, `%` as `%25`), so a value can't make another entity's key |
| Write sharding | `.WriteSharding(entity, shards, by)` adds `#SHARD#n` to the partition key, n from a stable hash of the `by` attribute |
| Size | An item over 400 KB is ARX4002 |
| Without a catalog | Nothing to resolve: the `ArxDynamoModel` declares every table's keys and ids, and DynamoDB declares no column types. The SQL providers' `SchemaResolution` doesn't apply |

## Reference

### Value types

| .NET value | Stored as |
|---|---|
| `string` | S (string); an empty key value is ARX1001 |
| `char` | S |
| a plain `enum` | refused (ARX1007): write `(int)value` or `value.ToString()`, or use a selection set |
| `int`, `long`, `short`, `byte` and the unsigned types | N (number) |
| `decimal` | N, exact up to 38 digits (more is ARX4001) |
| `double`, `float` | N; NaN and infinity are ARX4001 |
| `bool` | BOOL |
| `Guid` | S, `D` format (`5f0c…-…`) |
| `byte[]`, `ArxBytea` | B (binary) |
| `ArxJsonB` | M (map) or L (list), its JSON parsed |
| an array or a list | L, a list of the values above |
| `ArxSelectionSetValue` | its code, checked against the catalog |
| `ArxEntityReference` | the referenced id, checked against the catalog (ARX1005) |
| `null` | NULL |

### Dates and times

DynamoDB has no date type: ARX stores dates as sortable text, so a sort key or a condition on a date compares in time
order.

| .NET value | Stored as |
|---|---|
| `DateTime` (UTC) | S, `2026-11-09T10:30:00.0000000Z` (every tick kept) |
| `DateTime` (Local) | converted to UTC |
| `DateTime` (Unspecified) | taken as UTC |
| `DateTimeOffset` | its UTC instant (the offset isn't kept) |
| `DateOnly` | S, `2026-11-09` |
| `TimeOnly`, `TimeSpan` | S (`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 DynamoDB Local) and the PartiQL statement that checks what it did. `service` is the application's `ArxDynamoDbService`; 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-dynamodb-sample-application.md.

### Create one record, in either design

Basics 1 of Create.

The smallest create, in the two designs DynamoDB tables are made in: an entity with a table of its own, then a customer and an order sharing one table through key templates.

Good to know:

The model says where each entity goes; the code that creates it is the same in both designs. DynamoDB generates no ids: the id is one of the values you give. A create never overwrites: an item with the same key is ARX3001.

Usings:

```csharp
using ARX.SDK.DynamoDb.Model;
using ARX.SDK.DynamoDb.Services;
using ARX.SDK.Services;
using Amazon.DynamoDBv2;
```

#### 1. A table of its own

A product in its own table, keyed by its SKU.

In scope: `client` (IAmazonDynamoDB).

```csharp
var model = ArxDynamoModel.Create()
    .Table("product", partitionKey: "sku")
    .Build();

var service = new ArxDynamoDbService(client, model);

var product = new ArxEntity("product");
product["sku"] = "SKU-D101";
product["name"] = "Trail running shoe";
product["price"] = 129.90m;

await service.CreateAsync(product);

Console.WriteLine("created product SKU-D101");
```

Check it in the database:

```sql
SELECT * FROM "product" WHERE "sku" = 'SKU-D101'
```

Expect: 1 item, name "Trail running shoe", price 129.9.

#### 2. One table for customers and orders

A customer and its first order in one table: ARX writes PK, SK and entity_type from the model's templates.

In scope: `service` (ArxDynamoDbService).

```csharp
var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D101";
customer["name"] = "Northwind Traders";
ArxRecordId id = await service.CreateAsync(customer);

var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D101";
order["order_number"] = "SO-D101";
order["status"] = "OPEN";
order["ordered_on"] = "2026-11-09";
order["total"] = 250m;
await service.CreateAsync(order);

Console.WriteLine($"created customer {id.AsString()} and order SO-D101");
```

Check it in the database:

```sql
SELECT * FROM "crm" WHERE "PK" = 'CUSTOMER#C-D101'
```

Expect: 2 items: SK "ORDER#SO-D101" (entity_type sales_order, GSI1PK "STATUS#OPEN") and SK "PROFILE" (entity_type customer).

### Catch the errors a create meets

Basics 2 of Create.

The errors a create meets most, in three scenarios: a key that exists, a key attribute left out, and an id declared as generated, which DynamoDB can't do.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.DynamoDb.Model;
using ARX.SDK.DynamoDb.Services;
using ARX.SDK.Services;
using Amazon.DynamoDBv2;
```

#### 1. A key that exists

The same customer twice: ARX3001; the first stays as it was.

In scope: `service` (ArxDynamoDbService).

```csharp
var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D401";
customer["name"] = "Fabrikam";
await service.CreateAsync(customer);

customer["name"] = "Fabrikam (copy)";

try
{
    await service.CreateAsync(customer);
}
catch (ArxDatabaseException exception) when (exception.Code == ArxErrorCode.UniqueViolation)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
SELECT "name" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D401' AND "SK" = 'PROFILE'
```

Expect: 1 item, name "Fabrikam".

#### 2. A key attribute left out

An order without its customer_id: its key can't be made, so nothing is sent.

In scope: `service` (ArxDynamoDbService).

```csharp
var order = new ArxEntity("sales_order");
order["order_number"] = "SO-D402";

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

Check it in the database:

```sql
SELECT "SK" FROM "crm" WHERE "SK" = 'ORDER#SO-D402'
```

Expect: no items.

#### 3. A generated id

An id declared as generated: DynamoDB generates nothing, so ARX8001 with the advice to supply one.

In scope: `client` (IAmazonDynamoDB).

```csharp
var catalog = ArxSchemaCatalog.Create().Table("product", ArxIdColumn.Generated<string>("sku")).Build();
var service = new ArxDynamoDbService(client, ArxDynamoModel.Create().Table("product", partitionKey: "sku").Build(), catalog);

var product = new ArxEntity("product");
product["sku"] = "SKU-D403";

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

Check it in the database:

```sql
SELECT "sku" FROM "product" WHERE "sku" = 'SKU-D403'
```

Expect: no items.

### Store every kind of value

Real case 1 of Create.

What each .NET value becomes in DynamoDB, in two scenarios: numbers, text and flags; then dates, documents, lists and picklists.

Good to know:

- Numbers are N (up to 38 digits); NaN and infinity are refused. Text is S, booleans BOOL, bytes B, a Guid its text.
- Dates are sortable ISO 8601 text in UTC with seven fraction digits (an Unspecified DateTime is taken as UTC); a DateOnly is `yyyy-MM-dd`.
- `ArxJsonB` is a map; a list or an array is a list.

Usings:

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

#### 1. Numbers, text and flags

A product with numbers of every kind, a flag, bytes and a Guid.

In scope: `service` (ArxDynamoDbService).

```csharp
var product = new ArxEntity("product");
product["sku"] = "SKU-D501";
product["name"] = "Camp stove";
product["stock"] = 42;
product["weight_kg"] = 1.25;
product["price"] = 89.99m;
product["is_active"] = true;
product["barcode"] = new byte[] { 0x01, 0x02, 0x03 };
product["supplier_ref"] = Guid.Parse("3f2504e0-4f89-41d3-9a0c-0305e82c3301");

await service.CreateAsync(product);

Console.WriteLine("created product SKU-D501");
```

Check it in the database:

```sql
SELECT * FROM "product" WHERE "sku" = 'SKU-D501'
```

Expect: stock 42, weight_kg 1.25, price 89.99, is_active true, supplier_ref "3f2504e0-4f89-41d3-9a0c-0305e82c3301".

#### 2. Dates, documents, lists and picklists

An order with dates, an embedded address, a list of tags and a picklist value.

In scope: `service` (ArxDynamoDbService).

```csharp
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D501";
order["order_number"] = "SO-D501";
order["ordered_on"] = "2026-11-09";
order["status"] = new ArxSelectionSetValue("OrderStatus", "OPEN");
order["placed_at"] = new DateTime(2026, 11, 9, 14, 30, 0, DateTimeKind.Utc);
order["ship_by"] = new DateOnly(2026, 11, 12);
order["ship_to"] = new ArxJsonB { SerializedValue = "{\"street\":\"1 Harbour Road\",\"city\":\"Porto\"}" };
order["tags"] = new[] { "gift", "express" };

await service.CreateAsync(order);

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

Check it in the database:

```sql
SELECT "placed_at", "ship_by", "ship_to", "tags", "status" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D501' AND "SK" = 'ORDER#SO-D501'
```

Expect: placed_at "2026-11-09T14:30:00.0000000Z", ship_by "2026-11-12", ship_to a map with city "Porto", tags ["gift", "express"], status "OPEN".

### Catch an injection attempt

Real case 2 of Create.

A form whose fields become attributes, attacked three ways: an expression placeholder as a field name, SQL in a field name, and key separators in a value meant to land on another customer's key.

Good to know:

Names with `#`, `:` or SQL syntax are ARX1017 before anything is sent: names reach DynamoDB only as placeholders. Values in key templates are escaped, so `#` in a value can't make another entity's key.

Usings:

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

#### 1. A placeholder as a field name

A field named "#pk": ARX1017.

In scope: `service` (ArxDynamoDbService).

```csharp
var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D601";
customer["#pk"] = "CUSTOMER#someone-else";

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

Check it in the database:

```sql
SELECT "PK" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D601'
```

Expect: no items.

#### 2. SQL in a field name

SQL in a field name: ARX1017 too.

In scope: `service` (ArxDynamoDbService).

```csharp
var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D602";
customer["name'; DROP TABLE crm; --"] = "x";

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

Check it in the database:

```sql
SELECT "PK" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D602'
```

Expect: no items.

#### 3. A separator in a key value

An order number "x#ORDER#y": escaped in the key, so it stays this customer's order.

In scope: `service` (ArxDynamoDbService).

```csharp
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D603";
order["order_number"] = "x#ORDER#y";
order["status"] = "OPEN";
order["ordered_on"] = "2026-11-09";

await service.CreateAsync(order);

Console.WriteLine("stored, its key escaped");
```

Check it in the database:

```sql
SELECT "SK", "order_number" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D603'
```

Expect: 1 item, SK "ORDER#x%23ORDER%23y", order_number "x#ORDER#y".

### Create in an ARX transaction

Advanced case 1 of Create.

Writes across tables in one ARX transaction, in two scenarios: committed together, and a commit that fails and writes nothing.

Good to know:

DynamoDB has no open transaction: ARX collects the writes and sends them as one `TransactWriteItems` at the commit, all or nothing, across tables, up to 100. A failing write (a key that exists) shows at the commit.

Usings:

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

#### 1. Committed together

A customer (single table) and a product (its own table) committed together.

In scope: `service` (ArxDynamoDbService).

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

var customer = new ArxEntity("customer");
customer["customer_id"] = "C-D701";
customer["name"] = "Wide World Importers";
await service.CreateAsync(customer, transaction);

var product = new ArxEntity("product");
product["sku"] = "SKU-D701";
product["name"] = "Import crate";
await service.CreateAsync(product, transaction);

await transaction.CommitAsync();
Console.WriteLine("committed the customer and the product");
```

Check it in the database:

```sql
SELECT "name" FROM "product" WHERE "sku" = 'SKU-D701'
```

Expect: 1 item, name "Import crate".

#### 2. A commit that fails

The second customer repeats C-D701: the commit fails and the new product isn't written either.

In scope: `service` (ArxDynamoDbService).

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

var product = new ArxEntity("product");
product["sku"] = "SKU-D702";
await service.CreateAsync(product, transaction);

var duplicate = new ArxEntity("customer");
duplicate["customer_id"] = "C-D701";
await service.CreateAsync(duplicate, transaction);

try
{
    await transaction.CommitAsync();
}
catch (ArxDatabaseException exception)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
SELECT "sku" FROM "product" WHERE "sku" = 'SKU-D702'
```

Expect: no items.

### A security check around creates

Advanced case 2 of Create.

A security check around creates (`SecurityGuard`, in the project root), in three scenarios: a normal request, an attack that is logged, and repeated attacks that block the user.

Good to know:

The guard lets every ARX error through except ARX1017 SuspectedInjection, which it logs (user, action, ARX's evidence), answers with a plain message, and counts: three strikes block the user. The real fix is to map posted fields to names from a fixed list, so user input never becomes a name.

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

A normal sign-up goes through untouched.

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

```csharp
var result = await SignUpAsync(service, guard, "ana", new() { ["customer_id"] = "C-D901", ["name"] = "Ana" });

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

Defined in the sample, used above:

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

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

    return guard.RunAsync(user, "sign up", NameSource.UserInput, () => service.CreateAsync(customer));
}
```

Check it in the database:

```sql
SELECT "name" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D901' AND "SK" = 'PROFILE'
```

Expect: 1 item, name "Ana".

#### 2. An attack is logged

A field named like an expression placeholder: refused, logged, a plain message for the user.

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

```csharp
var result = await SignUpAsync(service, guard, "mallory", new() { ["customer_id"] = "C-D902", [":v0"] = "x" });

Console.WriteLine($"mallory sees: \"{result.ForTheUser}\"; log: {guard.Events[^1].Kind}, {guard.Events[^1].Detail}");
```

Check it in the database:

```sql
SELECT "PK" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D902'
```

Expect: no items.

#### 3. Repeated attacks block the user

Two more attempts: the third strike blocks mallory, and the next request isn't even tried.

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

```csharp
foreach (var probe in new[] { "name; DROP TABLE crm", "#PK" })
{
    await SignUpAsync(service, guard, "mallory", new() { ["customer_id"] = "C-D903", [probe] = "x" });
}

var blocked = await SignUpAsync(service, guard, "mallory", new() { ["customer_id"] = "C-D904", ["name"] = "Innocent looking" });

Console.WriteLine($"mallory blocked: {guard.IsBlocked("mallory")}; next request: {blocked.Outcome}");
```

Check it in the database:

```sql
SELECT "PK" FROM "crm" WHERE "PK" IN ['CUSTOMER#C-D903', 'CUSTOMER#C-D904']
```

Expect: no items.

### Set up the service from DynamoDB's tables

Advanced case 3 of Create.

The service set up from DynamoDB's own description of its tables, in two scenarios: preload the lead table's key at startup, then create with Strict on, where only the declared attributes may be written.

Good to know:

DynamoDB stores the types of key attributes only, so the preload reads the key (as a business key, with its type) and you declare the other attributes you allow with `.Column`. With `Strict`, any other attribute is ARX1018.

Usings:

```csharp
using ARX.SDK;
using ARX.SDK.DynamoDb.Model;
using ARX.SDK.DynamoDb.Services;
using ARX.SDK.Models;
using ARX.SDK.Services;
using Amazon.DynamoDBv2;
```

#### 1. Preload at startup

Preload the lead table once, at startup, allowing two more attributes.

In scope: `client` (IAmazonDynamoDB), `model` (ArxDynamoModel).

```csharp
var catalog = await new ArxPreLoadTable(new ArxPreLoadTableRegister().PreLoadTable("lead")).LoadAsync(client, model, schema => schema
    .Column("lead", new ArxColumnDefinition("full_name", typeof(string), "S", true))
    .Column("lead", new ArxColumnDefinition("score", typeof(int), "N", true)));

catalog.TryGetTable("lead", out var lead);
Console.WriteLine($"lead: key {string.Join(", ", lead.Keys)}, attributes {string.Join(", ", lead.Columns.Keys)}");

return new ArxDynamoDbService(client, model, catalog) { Strict = true };
```

Note: In the AWS CLI: aws dynamodb describe-table --table-name lead --endpoint-url http://localhost:18000 KeySchema email (HASH), AttributeDefinitions email S.

#### 2. Create with Strict on

A lead with the declared attributes is created; a misspelt attribute is refused before DynamoDB.

In scope: `service` (ArxDynamoDbService).

```csharp
var lead = new ArxEntity("lead");
lead["email"] = "lead-d951@example.com";
lead["full_name"] = "Kim Abercrombie";
lead["score"] = 72;
await service.CreateAsync(lead);
Console.WriteLine("created lead-d951@example.com");

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

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

Check it in the database:

```sql
SELECT "email", "score" FROM "lead" WHERE "email" IN ['lead-d951@example.com', 'lead-d952@example.com']
```

Expect: 1 item, lead-d951@example.com with score 72.

### 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("Trail running shoe", "SKU-D601");

Console.WriteLine($"added Trail running shoe 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("product");
        record["name"] = name;
        record["sku"] = key;
        return data.CreateAsync(record);
    }
}
```

Check it in the database:

```sql
SELECT "name" FROM "product" WHERE "sku" = 'SKU-D601'
```

Expect: 1 item, Trail running shoe.

#### 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}");
```

Note: No query: the check describes every table of the model. 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("Hiking boot", "SKU-D602");

try
{
    var nobody = new ArxEntity("product") { RecordId = ArxRecordId.Of("sku", "SKU-D699") };
    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
SELECT "name" FROM "product" WHERE "sku" = 'SKU-D602'
```

Expect: 1 item, Hiking boot (the update found nothing to change).

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

Advanced case 5 of Create.

Use items whose attributes are named outside ARX's name rule, in three scenarios: declare an alias for each such attribute and create items through them, read and query by the aliases, and see what is still refused: the attribute's own name in a call, and an alias on an attribute the model names.

Good to know:

ARX accepts only names of ASCII letters, digits and underscores. Items written by other code may have attributes such as `list-price`, `Colour Name` or `size.label`: declare an alias for each in the schema catalog, `.Alias(table, attribute, databaseName)`. Code uses the attribute; ARX writes and filters on the item's attribute (a `.` in it is part of the name, not a path) and reads it back as the attribute.

Aliases are for ordinary attributes: the model names the keys, index keys and key templates' attributes as the table stores them, so an alias on one of those is ARX8001.

Usings:

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

Set up by the sample before its scenarios:

```csharp
var schema = ArxSchemaCatalog.Create()
    .Table("product", ArxIdColumn.Supplied<string>("sku"))
    .Alias("product", "list_price", "list-price")
    .Alias("product", "colour", "Colour Name")
    .Alias("product", "size_label", "size.label")
    .Build();
var service = new ArxDynamoDbService(dbContext.Client, dbContext.Model, schema);
```

#### 1. Create through the aliases

Create two products: each attribute is written to the item attribute its alias names.

In scope: `service` (ArxDynamoDbService).

```csharp
var shoe = new ArxEntity("product");
shoe["sku"] = "SKU-A601";
shoe["name"] = "Trail running shoe";
shoe["list_price"] = 89.90m;
shoe["colour"] = "Forest green";
shoe["size_label"] = "EU 42";

await service.CreateAsync(shoe);

var boot = new ArxEntity("product");
boot["sku"] = "SKU-A602";
boot["name"] = "Hiking boot";
boot["list_price"] = 129.00m;

await service.CreateAsync(boot);

Console.WriteLine("created SKU-A601 and SKU-A602");
```

Check it in the database:

```sql
SELECT "list-price", "Colour Name", "size.label" FROM "product" WHERE "sku" IN ['SKU-A601', 'SKU-A602']
```

Expect: 2 items: 89.9 with Forest green and EU 42, and 129.

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

Read an item by id and query by the aliases: every attribute comes back under its alias.

In scope: `service` (ArxDynamoDbService).

```csharp
var shoe = await service.RetrieveAsync("product", ArxRecordId.Of("sku", "SKU-A601")) ?? throw new InvalidOperationException("The product record isn't there.");

Console.WriteLine($"{shoe["sku"]}: {shoe["colour"]}, {shoe["size_label"]}, {shoe["list_price"]}");

var query = new ArxQueryExpression("product");
query.ColumnSet.AddColumns("name", "list_price");
query.Criteria.AddCondition("sku", ArxConditionOperator.Equal, "SKU-A602");
query.Criteria.AddCondition("list_price", ArxConditionOperator.GreaterThan, 100m);

var products = await service.RetrieveMultipleAsync(query);

foreach (var product in products.Entities)
{
    Console.WriteLine($"{product["name"]}: {product["list_price"]}");
}
```

Check it in the database:

```sql
SELECT "name", "list-price" FROM "product" WHERE "sku" = 'SKU-A602' AND "list-price" > 100
```

Expect: 1 item, Hiking boot, 129.

#### 3. What is still refused

The attribute's own name in a call, and an alias on the table's key, are refused.

In scope: `service` (ArxDynamoDbService).

```csharp
var change = new ArxEntity("product");
change["sku"] = "SKU-A602";
change["list-price"] = 119.00m;

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

var keyed = new ArxDynamoDbService(dbContext.Client, dbContext.Model, ArxSchemaCatalog.Create()
    .Table("product", ArxIdColumn.Supplied<string>("sku"))
    .Alias("product", "sku", "SKU Code")
    .Build());

var sock = new ArxEntity("product");
sock["sku"] = "SKU-A603";

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

Check it in the database:

```sql
SELECT "sku", "list-price" FROM "product" WHERE "sku" IN ['SKU-A602', 'SKU-A603']
```

Expect: 1 item, SKU-A602 at 129 (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 a product: 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 product = new ArxEntity("product");
product["sku"] = "SKU-D611";
product["name"] = "Trail running shoe";

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

var query = new ArxQueryExpression("product");
query.Criteria.AddCondition("sku", ArxConditionOperator.Equal, "SKU-D611");
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
SELECT "name" FROM "product" WHERE "sku" = 'SKU-D611'
```

Expect: 1 item, Trail running shoe.

#### 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("product") { RecordId = ArxRecordId.Of("sku", "SKU-D699") };
nobody["name"] = "Nobody";

try
{
    await data.UpdateAsync(nobody);
}
catch (ArxException exception) when (exception.Code == ArxErrorCode.RecordNotFound)
{
    logger.LogError(exception, "Renaming product {Sku} failed: {Code}, transient: {IsTransient}",
        "SKU-D699", ArxException.FormatCode(exception.Code), exception.IsTransient);
}
```

Check it in the database:

```sql
SELECT "sku" FROM "product" WHERE "sku" = 'SKU-D699'
```

Expect: no items (the update found no product 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 product = new ArxEntity("product");
product["sku"] = "SKU-D612";
product["name"] = "Hiking boot";
await data.CreateAsync(product);

Console.WriteLine("created Hiking boot: nothing logged");

var gone = new ArxEntity("product") { RecordId = ArxRecordId.Of("sku", "SKU-D698") };
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
SELECT "name" FROM "product" WHERE "sku" = 'SKU-D612'
```

Expect: 1 item, Hiking boot.

