Date:

Share:

How to Implement Optimistic Locking in .NET for Amazon DynamoDB

Related Articles

This article is sponsored by AWS and is part of my AWS series.

It is essential to ensure data consistency when multiple users or applications access the same data simultaneously.

Optimistic locking is an approach that allows multiple users to access and modify the same data simultaneously while preventing conflicts and maintaining consistency.

In this post, we’ll learn how to set up optimistic locking with DynamoDB table objects when building your apps.

I will use the .NET programming language for the application code.

Optimistic lock

Optimistic lock is a strategy to ensure that the client-side item you update (or delete) is the same as the item in the database.

With this, your database’s write is protected from being overwritten by others’ writes and vice versa.

In optimistic locking, data is assigned a version number or timestamp. Before making changes to this data, the user checks the version number or timestamp to ensure that another user has not modified it since it was last accessed.

For example, let’s say you retrieve an item from the database table and display it in a UI form that User 1 is working on, modifying it, and making some changes. During the time another user, User2, comes and returns the same item on his computer and makes an update.

When user 1 saves the data, it may accidentally overwrite the changes made by user 2. So when user 1 does the update, it should fail and force them to get the latest data along with the changes user 2 made and then reapply those changes to the new version of the object.

Amazon DynamoDB and Optimistic Locking

Conditional expressions allow us to achieve optimal locking when working with DynamoDB tables.

Conditional writing using ConditionExpressions Allow us to write data to the table only if specific conditions are met, such as if a certain attribute has an exact value, or the item’s version number matches a certain value.

To use conditional writes for optimistic locking in DynamoDB, you can follow these steps:

  1. Add a version number attribute to your table. This attribute will be used to track the version of each item in the table.
  2. When a user wants to update an item, retrieve the item from the table along with its version number.
  3. Change the item and increase the version number.
  4. When writing the item back into DynamoDB, specify a ConditionExpression To check the Version number attribute in the table matches the version number retrieved in step 2. If the condition is not met, the update will not be applied, and the user can be notified to refresh the data and try again.

.NET and DynamoDB optimistic locking

When using the object persistence model, which is the high-level API that uses the DynamoDBContext In the .NET SDK, Optimistic Locking support is available out of the box.

To use this feature, all you need to do is add a new property and assign the DynamoDBVersion attribute to, in the .NET class that represents the DynamoDB table object.

public class WeatherForecast
{
    public string CityName { get; set; }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string? Summary { get; set; }

    [DynamoDBVersion]
    public int? Version { get; set; }
}

When you first save the object, DynamoDBContext Assigns a version number and increments this value each time you update the item. It also automatically adds the ConditionExpression Check when saving the item to ensure optimal fit.

In the example above, since the Version property is an integer, DynamoDBContext Assigns a value of 0 when saving the item using the context for the first time. It automatically increments the value each time it saves and adds the ConditionExpression to ensure that the value is equal to the one before the increment.

Canceling or skipping optimistic lock checks

To disable optimistic lock checking when using DynamoDBContext Along with version tags, you can transfer the SkipVersionCheck Flag as truth.

[HttpPost("post-skip-versioning")]
public async Task PostSkipVersion(WeatherForecast data)
{
    data.Date = data.Date.Date;
    await _dynamoDbContext.SaveAsync(
        data,
        new DynamoDBOperationConfig()
        {
            SkipVersionCheck = true
        });
}

As shown above, this is done as part of the DynamoDBOperationConfig object, which is passed as an optional parameter along with the SaveAsync Contact DynamoDBContext.

You can also set this property at the DynamoDBContext level, which applies to all calls that use the context instance.

Optimistic locking and deletion of items

God Delete Method on DynamoDBContext There are two loads

  • Takes only the key values ​​→ Hash and range key
  • Takes the entire item for deletion.

When using a load that only takes the keys, it knows nothing about the version and ignores it when deleting the object.

When passing the entire item to the Delete function, the operation succeeds only if the version number matches the server.

.NET AmazonDynamoDBClient and Optimistic Locking

When using a low-level API, AmazonDynamoDBClientTo interact with DynamoDB, we can explicitly add ConditionExpression to achieve optimal locking.

If you are new to Condition Expressions, check out my blog post and associated video linked below.

How to ensure data consistency with DynamoDB state expressions from .NET applications

Conditional expressions allow you to specify constraints when writing data to an Amazon DynamoDB table. This allows you to specify a boolean expression that must be true for the action to proceed. Let’s learn about Condition Expressions and how to use it from .NET

Below is an example UpdateItemRequest which determines the ConditionExpression which checks the version number in the DynamoDB server item is the same as when we retrieved the item for the update.

var updateItemRequest = new UpdateItemRequest
{
    TableName = "TableName",
    Key = new Dictionary<string, AttributeValue>
    {
        { "id", new AttributeValue { S = "Id" } }
    },
    UpdateExpression = "set #myAttr = :newValue, #version = :newVersion",
    ConditionExpression = "#version = :oldVersion",
    ExpressionAttributeNames = new Dictionary<string, string>
    {
        { "#myAttr", "myAttribute" },
        { "#version", "version" }
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>
    {
        { ":newValue", new AttributeValue { S = item.MyAttribute } },
        { ":oldVersion", new AttributeValue { N = version.ToString() } },
        { ":newVersion", new AttributeValue { N = item.Version.ToString() } }
    }
};

If the versions match, our updater goes through and determines the new version number as well. Any other user/application updating the item must retrieve the item/version number before performing an update.

Source

Popular Articles