Posted in:

Azure Table Storage has the advantage of being very cheap, which means it often gets used when you have quite basic storage requirements, and don't want to shell out for a SQL or Cosmos database.

It works best when most of the time you are directly looking things up by their "key" (which in table storage is a combination of a "partition key" and a "row key").

Using ETags to for concurrency

When you update a row (known as an "entity") in table storage, you can make use of an ETag to ensure that your change hasn't conflicted with someone else's change. First, you read the existing entity, which will give you the current ETag, then update the properties you wish to modify, and then call Update on that entity, passing in the original ETag (which is transmitted in the If-Match header). If the ETag hasn't changed since you read, your update will be accepted, otherwise you'll get a HTTP 409 conflict response.

In this example I've declared a helper base class called TableEntity and my table is going to store RoomEntity objects:

// n.b. using the Azure.Data.Tables NuGet package
public class TableEntity : ITableEntity
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public ETag ETag { get; set; }
}
public class RoomEntity : TableEntity
{
    public string Name { get; set; }
    public string Location { get; set; }
    public int CurrentTemperature { get; set; }
    public bool WindowsOpen { get; set; }
}

And let's connect to table storage and add a new row...

var connectionString = "my storage account connection string";
var tableServiceClient = new TableServiceClient(connectionString);
var patchTestTable = tableServiceClient.GetTableClient("patchtest");
await patchTestTable.CreateIfNotExistsAsync();

var partitionKey = "EXAMPLE";
var rowKey = "1010";

// clean up from any previous test runs:
await patchTestTable.DeleteEntityAsync(partitionKey, rowKey);

var entity = new RoomEntity()
{
    PartitionKey = partitionKey,
    RowKey = rowKey,
    Name = "Cedar",
    Location = "London",
    CurrentTemperature = 19,
    WindowsOpen = false
};

var added = await patchTestTable.AddEntityAsync(entity);

And if we want to see the original ETag (which typically is a string like W/"datetime'2023-08-19T10%3A27%3A08.3147297Z'") we can get it like this:

var initialETag  = added.Headers.ETag.Value;

We can update it by modifying a property and calling UpdateEntityAsync. And I'll pass in the initial ETag to say that we should only update if no one else has changed the row since we initially created it.

entity.Name = "Oak";
await patchTestTable.UpdateEntityAsync(entity, initialETag);

If however I do the same thing again, and pass in the initial ETag, the update will fail with a RequestFailedException with the message "The update condition specified in the request was not satisfied". That's because when we updated the Name column, the entity was given a new ETag.

entity.Name = "Birch";
await patchTestTable.UpdateEntityAsync(entity, initialETag);

Of course sometimes you don't care about conflicts, and are happy for the most recent change to win. In this case you can use ETag.All (which is just the string "*"), to indicate that you want your update to overwrite whatever was already in that row.

So if we change our code to use ETag.All, the update will go through without error.

entity.Name = "Birch";
await patchTestTable.UpdateEntityAsync(entity, ETag.All);

However, this update runs the risk of potentially reverting someone else's change to another column (e.g. CurrentTemperature). Let's see how we can update only an individual column.

Updating columns individually (patching)

Imagine that our entities are being updated fairly regularly. We have a temperature sensor that is updating the CurrentTemperature column. And there is another sensor that periodically updates the WindowsOpen column.

The table storage SDK allows us to independently update the values in these columns, and in code the easiest way to do this is to define additional entities just for the columns you want to update:

public class TemperatureEntity : TableEntity
{
    public int CurrentTemperature { get; set; }
}
public class WindowsOpenEntity : TableEntity
{
    public bool WindowsOpen { get; set; }
}

This allows us to use the TableUpdateMode.Merge flag on UpdateEntityAsync to only update the properties in the object we pass in. So in this example we're only setting the WindowsOpen flag to true.

var windowsOpenUpdate = new WindowsOpenEntity()
{
    PartitionKey = partitionKey,
    RowKey = rowKey,
    WindowsOpen = true
};

// or ETag.All 
await patchTestTable.UpdateEntityAsync(windowsOpenUpdate, initialETag, TableUpdateMode.Merge);

The nice thing about this setup is that we can use ETag.All with these patches, and that saves us the time to look up the current ETag value, or the need to handle 409 conflicts, and still know that we're not going to overwrite changes to other columns in the entity.

var temperatureUpdate = new TemperatureEntity()
{
    PartitionKey = partitionKey,
    RowKey = rowKey,
    CurrentTemperature = 23
};
await patchTestTable.UpdateEntityAsync(temperatureUpdate, ETag.All, TableUpdateMode.Merge);