Skip to content

Transactions

Mark Lauter edited this page May 16, 2026 · 5 revisions

Transactions

Overview

DynamoDbLite supports transactional reads and writes with all-or-nothing semantics. Transactions evaluate conditions before executing any writes, and the entire operation is wrapped in a single SQLite transaction. See DynamoDB Transactions in the AWS docs.

TransactWriteItemsAsync

Executes up to 100 write actions atomically. All conditions are evaluated first; if any fail, no writes occur.

Signature

Task<TransactWriteItemsResponse> TransactWriteItemsAsync(
    TransactWriteItemsRequest request, CancellationToken cancellationToken = default)

Parameters

  • TransactItems (required) — list of 1-100 actions
  • ClientRequestToken — idempotency token (10-minute cache TTL)

Action types

Each TransactWriteItem contains exactly one action:

Action Description Supports ConditionExpression
Put Write a complete item Yes
Update Apply an UpdateExpression Yes
Delete Remove an item by key Yes
ConditionCheck Validate a condition without modifying data Yes (required)

Example

await client.TransactWriteItemsAsync(new TransactWriteItemsRequest
{
    TransactItems =
    [
        new TransactWriteItem
        {
            Put = new Put
            {
                TableName = "Accounts",
                Item = new Dictionary<string, AttributeValue>
                {
                    ["AccountId"] = new() { S = "acct-1" },
                    ["Balance"] = new() { N = "1000" }
                },
                ConditionExpression = "attribute_not_exists(AccountId)"
            }
        },
        new TransactWriteItem
        {
            Update = new Update
            {
                TableName = "Accounts",
                Key = new Dictionary<string, AttributeValue>
                {
                    ["AccountId"] = new() { S = "acct-2" }
                },
                UpdateExpression = "SET Balance = Balance - :amount",
                ConditionExpression = "Balance >= :amount",
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>
                {
                    [":amount"] = new() { N = "500" }
                }
            }
        },
        new TransactWriteItem
        {
            ConditionCheck = new ConditionCheck
            {
                TableName = "Config",
                Key = new Dictionary<string, AttributeValue>
                {
                    ["ConfigId"] = new() { S = "transfers-enabled" }
                },
                ConditionExpression = "#v = :true",
                ExpressionAttributeNames = new Dictionary<string, string>
                {
                    ["#v"] = "Value"
                },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>
                {
                    [":true"] = new() { BOOL = true }
                }
            }
        }
    ]
});

Execution phases

  1. Validation — verify table existence, key schemas, item uniqueness
  2. Pre-read — load existing items for condition evaluation and updates
  3. Condition evaluation — evaluate all ConditionExpressions; collect failures
  4. Update expression application — apply UpdateExpressions to produce new items
  5. Write — execute all puts/deletes in a single SQLite transaction

Error handling

If any condition fails, a TransactionCanceledException is thrown with per-action details:

try
{
    await client.TransactWriteItemsAsync(request);
}
catch (TransactionCanceledException ex)
{
    foreach (var reason in ex.CancellationReasons)
    {
        Console.WriteLine($"Code: {reason.Code}");    // "ConditionalCheckFailed" or "None"
        Console.WriteLine($"Message: {reason.Message}");
        // reason.Item contains the existing item if ReturnValuesOnConditionCheckFailure = ALL_OLD
    }
}

ReturnValuesOnConditionCheckFailure

Each action supports ReturnValuesOnConditionCheckFailure:

  • NONE (default) — no item returned on failure
  • ALL_OLD — returns the existing item in the CancellationReason.Item field
new TransactWriteItem
{
    Put = new Put
    {
        TableName = "Users",
        Item = myItem,
        ConditionExpression = "attribute_not_exists(UserId)",
        ReturnValuesOnConditionCheckFailure = "ALL_OLD"
    }
}

Idempotency (ClientRequestToken)

If ClientRequestToken is provided, the response is cached for 10 minutes. Subsequent requests with the same token return the cached response without re-executing the transaction. The cache lives in process memory and is cleaned lazily — expired entries are evicted only when a later request with a ClientRequestToken arrives, not on a background timer.

await client.TransactWriteItemsAsync(new TransactWriteItemsRequest
{
    ClientRequestToken = "unique-request-id-123",
    TransactItems = [/* ... */]
});

Constraints

  • Minimum 1 action per transaction — an empty TransactItems list throws AmazonDynamoDBException
  • Maximum 100 actions per transaction
  • Each item can appear in at most one action (enforced by table + PK + SK uniqueness)
  • Update actions require an UpdateExpression

TransactGetItemsAsync

Retrieves up to 100 items atomically from one or more tables.

Signature

Task<TransactGetItemsResponse> TransactGetItemsAsync(
    TransactGetItemsRequest request, CancellationToken cancellationToken = default)

Parameters

  • TransactItems (required) — list of 1-100 Get actions
    • TableName (required) — target table
    • Key (required) — primary key
    • ProjectionExpression — attributes to return
    • ExpressionAttributeNames — name substitutions

Example

var response = await client.TransactGetItemsAsync(new TransactGetItemsRequest
{
    TransactItems =
    [
        new TransactGetItem
        {
            Get = new Get
            {
                TableName = "Users",
                Key = new Dictionary<string, AttributeValue>
                {
                    ["UserId"] = new() { S = "user-1" }
                }
            }
        },
        new TransactGetItem
        {
            Get = new Get
            {
                TableName = "Orders",
                Key = new Dictionary<string, AttributeValue>
                {
                    ["CustomerId"] = new() { S = "user-1" },
                    ["OrderId"] = new() { S = "order-1" }
                },
                ProjectionExpression = "OrderId, #s, Total",
                ExpressionAttributeNames = new Dictionary<string, string>
                {
                    ["#s"] = "Status"
                }
            }
        }
    ]
});

// Responses are ordered to match the request
var user = response.Responses[0].Item;   // may be null if not found
var order = response.Responses[1].Item;

Response

Each ItemResponse in Responses contains an Item (the retrieved attributes) or null if the item doesn't exist.

Constraints

  • Minimum 1 Get action — an empty TransactItems list throws AmazonDynamoDBException
  • Maximum 100 Get actions per call

Parity notes

  • All conditions are evaluated atomically (single SQLite transaction)
  • ClientRequestToken idempotency uses an in-memory cache with 10-minute TTL (not persisted across restarts)
  • ReturnConsumedCapacity is accepted but has no effect — capacity tracking is out of scope
  • Index maintenance happens within the same transaction

Out of scope

ExecuteStatementAsync, BatchExecuteStatementAsync, and ExecuteTransactionAsync (PartiQL) throw NotSupportedException. PartiQL is out of scope for this emulator.

Next steps

  • API Parity — full compatibility matrix and behavioral differences

Clone this wiki locally