Skip to main content

DynamoDB Transactions in C#

Perform atomic, ACID-compliant transactions on DynamoDB using FluentDynamoDB. Transactions ensure that multiple operations across one or more tables either all succeed or all fail together, maintaining data consistency. This guide demonstrates write and read transactions using the three API patterns.

Write Transactions

// Transfer money between accounts atomically
await DynamoDbTransactions.Write
.Add(accountTable.Update(fromAccountId)
.Set(x => new AccountUpdate { Balance = x.Balance - amount })
.Where(x => x.Balance >= amount))
.Add(accountTable.Update(toAccountId)
.Set(x => new AccountUpdate { Balance = x.Balance + amount }))
.Add(transactionTable.Put(new Transaction
{
TransactionId = Guid.NewGuid().ToString(),
Amount = amount,
Timestamp = DateTime.UtcNow
}))
.ExecuteAsync();

Conditional Put

// Create order only if it doesn't exist
await DynamoDbTransactions.Write
.Add(orderTable.Put(order)
.Where(x => x.OrderId.AttributeNotExists()))
.Add(inventoryTable.Update(productId)
.Set(x => new InventoryUpdate { Quantity = x.Quantity - orderQuantity })
.Where(x => x.Quantity >= orderQuantity))
.ExecuteAsync();

ConditionCheck

// Verify inventory before confirming order
await DynamoDbTransactions.Write
.Add(inventoryTable.ConditionCheck(productId)
.Where(x => x.Quantity >= requiredQuantity))
.Add(orderTable.Update(orderId)
.Set(x => new OrderUpdate { Status = "confirmed" }))
.ExecuteAsync();

Read Transactions

// Get consistent snapshot of related data
var (user, account, order) = await DynamoDbTransactions.Get
.Add(userTable.Get(userId))
.Add(accountTable.Get(accountId))
.Add(orderTable.Get(orderId))
.ExecuteAndMapAsync<User, Account, Order>();

// Access results
Console.WriteLine($"User: {user?.Name}");
Console.WriteLine($"Balance: {account?.Balance}");
Console.WriteLine($"Order Status: {order?.Status}");

Read with Index Access

// Get response and access items by index
var response = await DynamoDbTransactions.Get
.Add(userTable.Get("user1"))
.Add(userTable.Get("user2"))
.Add(orderTable.Get("order1"))
.ExecuteAsync();

var user1 = response.GetItem<User>(0);
var user2 = response.GetItem<User>(1);
var order = response.GetItem<Order>(2);

RequireWriteTransaction Attribute

For entities that must always be modified within transactions, use the [RequireWriteTransaction] attribute:

[DynamoDbTable("FinancialTransactions")]
[RequireWriteTransaction]
public partial class Transaction
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string TransactionId { get; set; } = string.Empty;

[DynamoDbAttribute("amount")]
public decimal Amount { get; set; }

[DynamoDbAttribute("timestamp")]
public DateTime Timestamp { get; set; }
}

When applied, non-transactional writes throw InvalidOperationException:

// ✅ Works - within transaction
await DynamoDbTransactions.Write
.Add(table.Transactions.Put(transaction))
.ExecuteAsync();

// ❌ Throws InvalidOperationException
await table.Transactions.Put(transaction).PutAsync();
// Error: "Entity 'Transaction' is marked with [RequireWriteTransaction] and cannot be
// modified outside of a transaction. Use DynamoDbTransactions.Write() to perform this operation."

Error Handling

try
{
await DynamoDbTransactions.Write
.Add(accountTable.Update(accountId)
.Set(x => new AccountUpdate { Balance = x.Balance - amount })
.Where(x => x.Balance >= amount))
.ExecuteAsync();
}
catch (TransactionCanceledException ex)
{
foreach (var reason in ex.CancellationReasons)
{
if (reason.Code == "ConditionalCheckFailed")
Console.WriteLine("Insufficient balance");
else if (reason.Code == "TransactionConflict")
Console.WriteLine("Concurrent modification - retry");
}
}

Learn More