# Retrieve with ARX.SDK on Amazon DynamoDB

## What a read does

| Rule | On DynamoDB |
|---|---|
| One item | By its full key (`GetItem`), strongly consistent by default; not found is null. By other columns that give a partition key (of the table or an index): a query, and several items are ARX2003 |
| Lookups | A declared lookup reads back as an `ArxEntityReference` whose `Name` is null: DynamoDB makes no joins, so a `.DisplayName(...)` in the catalog isn't read (read the referenced item yourself where you need its name) |
| Attribute names | Case-sensitive in DynamoDB: an attribute the catalog declares (the id, a key, the version, the display name, a declared column) is stored with its declared spelling whatever the case of the entity's name; an undeclared one by its exact spelling (`Name` and `name` are two attributes) |
| Execute | Not offered: DynamoDB has no stored procedures (ARX8001) |

## Reference

### Value types read back

| Stored as | Read back as |
|---|---|
| S (string) | `string`, or the preloaded type (`Guid`, `DateTime`, `DateOnly`, …) |
| N (number) | `decimal` (a `double` past decimal's range), or the preloaded type |
| BOOL | `bool` |
| B (binary) | `byte[]` |
| M (map) | `ArxJsonB` |
| L (list) | `string[]` or `decimal[]` when every element is that kind; otherwise `ArxJsonB` |
| SS, NS, BS (sets) | `string[]`, a number array, `byte[][]` |
| NULL, or no attribute | null |
| a declared lookup | `ArxEntityReference` |

## The samples

Every Retrieve 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.

### Retrieve one record

Basics 1 of Retrieve.

Read one item by its key, in three scenarios: a product by its id, an order by its full key with its customer read back as a lookup, and an id no item has.

Good to know:

One `GetItem`, strongly consistent by default (it sees every write that succeeded before it). An order's key is its customer and its number, so give both. The catalog's lookup reads `customer_id` back as an `ArxEntityReference`. Not found is null.

Usings:

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

Set up by the sample before its scenarios:

```csharp
var product = new ArxEntity("product");
product["sku"] = "SKU-D401";
product["name"] = "Hiking pole";
product["price"] = 34.50m;
await service.CreateAsync(product);
var order = new ArxEntity("sales_order");
order["customer_id"] = "C-D401";
order["order_number"] = "SO-D401";
order["status"] = "OPEN";
order["ordered_on"] = "2026-11-08";
await service.CreateAsync(order);
```

#### 1. By id

A product by its SKU, the key and the id.

In scope: `service` (ArxDynamoDbService).

```csharp
ArxEntity? found = await service.RetrieveAsync("product", "SKU-D401");

Console.WriteLine($"{found!["name"]}, {found["price"]}");
```

Check it in the database:

```sql
SELECT "name", "price" FROM "product" WHERE "sku" = 'SKU-D401'
```

Expect: 1 item, Hiking pole, 34.5.

#### 2. By the full key

An order by its customer and number; its customer_id comes back as a reference.

In scope: `service` (ArxDynamoDbService).

```csharp
var match = new ArxEntity("sales_order");
match["customer_id"] = "C-D401";
match["order_number"] = "SO-D401";

ArxEntity? found = await service.RetrieveAsync(match);
var customer = (ArxEntityReference)found!["customer_id"]!;

Console.WriteLine($"{found["order_number"]}: {found["status"]}, customer {customer.EntityName} {customer.RecordId!.AsString()}");
```

Check it in the database:

```sql
SELECT "order_number", "status", "customer_id" FROM "crm" WHERE "PK" = 'CUSTOMER#C-D401' AND "SK" = 'ORDER#SO-D401'
```

Expect: 1 item, SO-D401, OPEN, customer_id "C-D401".

#### 3. An id no item has

A SKU no item has: null.

In scope: `service` (ArxDynamoDbService).

```csharp
ArxEntity? missing = await service.RetrieveAsync("product", "SKU-D499");

Console.WriteLine($"found: {missing != null}");
```

Check it in the database:

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

Expect: no items.

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

Advanced case 1 of Retrieve.

The service set up from DynamoDB's description of the lead table, in two scenarios: preload at startup, then read with Strict on, where only the declared attributes may be asked for.

Good to know:

With `Strict`, the preloaded and declared attributes are an allow list for reads too: asking for one outside it is ARX1018, so a typo is caught rather than read back as a missing value.

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;
```

Set up by the sample before its scenarios:

```csharp
var lead = new ArxEntity("lead");
lead["email"] = "lead-d481@example.com";
lead["full_name"] = "Ravi Kumar";
lead["score"] = 65;
await service.CreateAsync(lead);
```

#### 1. Preload at startup

Preload the lead table once, at startup.

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 table);
Console.WriteLine($"lead: key {string.Join(", ", table.Keys)}, attributes {string.Join(", ", table.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).

#### 2. Read with Strict on

Read the declared attributes; a misspelt one is ARX1018.

In scope: `service` (ArxDynamoDbService).

```csharp
var match = new ArxEntity("lead");
match["email"] = "lead-d481@example.com";

var found = await service.RetrieveAsync(match, new ArxRetrieveOptions { Match = ArxMatch.Key("email"), Columns = new ArxColumnSet("full_name", "score") });
Console.WriteLine($"{found!["full_name"]}, score {found["score"]}");

try
{
    await service.RetrieveAsync(match, new ArxRetrieveOptions { Match = ArxMatch.Key("email"), Columns = new ArxColumnSet("full_name", "scroe") });
}
catch (ArxValidationException exception) when (exception.Code == ArxErrorCode.NotPreloaded)
{
    Console.WriteLine(exception.Message);
}
```

Check it in the database:

```sql
SELECT "full_name", "score" FROM "lead" WHERE "email" = 'lead-d481@example.com'
```

Expect: 1 item, Ravi Kumar, 65.

