3

I need to implement a transaction in C# with AWS DynamoDb as database

I checked the official website but don't see any example with C#

Below are my different Db operations.

 public class DbHandler
 {
     private readonly IConfiguration _configuration;
     private readonly RegionEndpoint _region;
     private readonly AmazonDynamoDBClient _dynamoClient; 

    public DbHandler(IConfiguration configuration)
     {
         _configuration = configuration;
         var awsSettings = configuration.GetSection("AWS:DynamoDb");
         _region = RegionEndpoint.GetBySystemName(awsSettings["Region"]);
         _dynamoClient = SetDynamoClient(awsSettings);
     }

     public async Task<EventTO> Add(EventTO eventObj)
     {
         try
         {
             //Db Operation#1
             await _dynamoClient.PutItemAsync(
             tableName: _configuration.GetSection("AWS:DynamoDb")["Table1"],
             item: SetEventObject(eventObj));

             //Db Operation#2
             await _dynamoClient.PutItemAsync(tableName: _configuration.GetSection("AWS:DynamoDb")["Table2"], someotherObj);
             return eventObj;
         }
         catch (Exception ex)
         {
             throw;
         }

     }
 }

I am using the Low Level API

    private Dictionary<string, AttributeValue> SetEventObject(EventTO eventObj)
        {
            //DynamoDb - Using Low Level API
            var attributes = new Dictionary<string, AttributeValue>
            {
                //EventId
                {
                    nameof(eventObj.EventId),
                    new AttributeValue
                    {
                        S =eventObj.EventId
                    }
                },
                //Event Title
                {
                    nameof(eventObj.Title),
                    new AttributeValue
                    {
                        S = eventObj.Title.Trim()
                    }
                }
            };
            return attributes;
        }

I want to know how to implement Transaction using the Low Level API in C# for AWS DynamoDb?

Thanks!

André Silva
  • 1,149
  • 9
  • 30
Kgn-web
  • 7,047
  • 24
  • 95
  • 161
  • As far as I know, it doesn't exist yet. But, Java and C# are pretty close, you might be able to convert some of their code fairly easily. I've had to do that before and it worked well. https://github.com/awslabs/dynamodb-transactions/tree/master/src/main/java/com/amazonaws/services/dynamodbv2/transactions – TrialAndError Aug 22 '19 at 23:11

2 Answers2

2

How about IAmazonDynamoDB.TransactWriteItemsAsync (TransactWriteItemsRequest)?

https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/DynamoDBv2/MIDynamoDBTransactWriteItemsTransactWriteItemsRequest.html

2

For those looking for something more detailed, the following translates the Java snippets the question links to at https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-example.html into their C# equivalents.

Keep in mind that the original Java example is a contrived one. In a production environment, you would want to do things like separate business logic from writing to a console and separate the various parts of building a transaction into smaller methods to adhere to SRP.

Prerequisites

    private const string CUSTOMER_TABLE_NAME = "Customers";
    private const string CUSTOMER_PARTITION_KEY = "CustomerId";
    private const string PRODUCT_TABLE_NAME = "ProductCatalog";
    private const string PRODUCT_PARTITION_KEY = "ProductId";
    private const string ORDER_PARTITION_KEY = "OrderId";
    private const string ORDER_TABLE_NAME = "Orders";
  • both the read and write Java examples assume method signatures taking two string parameters named productKey and orderId
  • You'll need the following two using statements in your class:
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;

Making an order

Validate the customer

    var customerId = "09e8e9c8-ec48";
    var customerItemKey = new Dictionary<string, AttributeValue>
    {
        { CUSTOMER_PARTITION_KEY, new AttributeValue(customerId) }
    };

    var checkCustomerValid = new ConditionCheck()
    {
        TableName = CUSTOMER_TABLE_NAME,
        Key = customerItemKey,
        ConditionExpression = "attribute_exists(" + CUSTOMER_PARTITION_KEY + ")"
    };

Update the product status

    var productItemKey = new Dictionary<string, AttributeValue>
    {
        { PRODUCT_PARTITION_KEY, new AttributeValue(productKey) }
    };

    var expressionAttributeValues = new Dictionary<string, AttributeValue>
    {
        { ":new_status", new AttributeValue("SOLD") },
        { ":expected_status", new AttributeValue("IN_STOCK") }
    };

    var markItemSold = new Update()
    {
        TableName = PRODUCT_TABLE_NAME,
        Key = productItemKey,
        UpdateExpression = "SET ProductStatus = :new_status",
        ExpressionAttributeValues = expressionAttributeValues,
        ConditionExpression = "ProductStatus = :expected_status",
        ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD
    };

Create the order

    var orderItem = new Dictionary<string, AttributeValue>
    {
        { ORDER_PARTITION_KEY, new AttributeValue(orderId) },
        { PRODUCT_PARTITION_KEY, new AttributeValue(productKey) },
        { CUSTOMER_PARTITION_KEY, new AttributeValue(customerId) },
        { "OrderStatus", new AttributeValue("CONFIRMED") },
        { "OrderTotal", new AttributeValue("100") }
    };

    var createOrder = new Put()
    {
        TableName = ORDER_TABLE_NAME,
        Item = orderItem,
        ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD,
        ConditionExpression = "attribute_not_exists(" + ORDER_PARTITION_KEY + ")"
    };

Run the transaction

    var actions = new List<TransactWriteItem>()
    {
        new TransactWriteItem() {ConditionCheck = checkCustomerValid },
        new TransactWriteItem() { Update = markItemSold },
        new TransactWriteItem() { Put = createOrder }
    };

    var placeOrderTransaction = new TransactWriteItemsRequest()
    {
        TransactItems = actions,
        ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL
    };

    // Run the transaction and process the result.
    try
    {
        _ = await client.TransactWriteItemsAsync(placeOrderTransaction);
        Console.WriteLine("Transaction Successful");
    }
    catch (ResourceNotFoundException rnf)
    {
        Console.Error.WriteLine("One of the table involved in the transaction is not found" + rnf.Message);
    }
    catch (InternalServerErrorException ise)
    {
        Console.Error.WriteLine("Internal Server Error" + ise.Message);
    }
    catch (TransactionCanceledException tce)
    {
        Console.Error.WriteLine("Transaction Canceled " + tce.Message);
    }

Reading the order details

    var productItemKey = new Dictionary<string, AttributeValue>
    {
        { PRODUCT_PARTITION_KEY, new AttributeValue(productKey) }
    };
    var orderKey = new Dictionary<string, AttributeValue>
    {
        { ORDER_PARTITION_KEY, new AttributeValue(orderId) }
    };
    var readProductSold = new Get()
    {
        TableName = PRODUCT_TABLE_NAME,
        Key = productItemKey
    };
    var readCreatedOrder = new Get()
    {
        TableName = ORDER_TABLE_NAME,
        Key = orderKey
    };

    var getActions = new List<TransactGetItem>()
    {
        new TransactGetItem(){Get = readProductSold },
        new TransactGetItem(){Get = readCreatedOrder}
    };

    var readCompletedOrder = new TransactGetItemsRequest()
    {
        TransactItems = getActions,
        ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL
    };

    // Run the transaction and process the result.
    try
    {
        var result = await client.TransactGetItemsAsync(readCompletedOrder);
        Console.WriteLine(result.Responses);
    }
    catch (ResourceNotFoundException rnf)
    {
        Console.Error.WriteLine("One of the table involved in the transaction is not found" + rnf.Message);
    }
    catch (InternalServerErrorException ise)
    {
        Console.Error.WriteLine("Internal Server Error" + ise.Message);
    }
    catch (TransactionCanceledException tce)
    {
        Console.Error.WriteLine("Transaction Canceled" + tce.Message);
    }
  • An important limit on the use of DynamoDB transactions in C#: currently, Amazon's `AttributeValue` type only contains constructors (besides the empty constructor) taking `string` or `List` arguments. Since transactions are always composed from dictionaries whose key-value pair's value type is `AttributeValue`, this causes an issue if your transaction objects have e.g. lists of lists or any nesting of lists greater than one that you need to map into a `Put`. To deal with this, you may need to break up larger document objects. – Jacob Archambault Feb 07 '23 at 04:04