Using the new C# Azure.Data.Tables SDK with Azure Cosmos DB

Last month, the Azure SDK team released a new library for Azure Tables for .NET, Java, JS/TS and Python. This release brings the Table SDK in line with other Azure SDKs and they use the specific Azure Core packages for handling requests, errors and credentials.

If you're making a choice between Azure Cosmos DB Table API and regular Azure Table Storage, I'd recommend reading the following article.

In this article, I'll show you how we can perform simple operations against a Azure Cosmos DB Table API account using the new Azure.Data.Table C# SDK. Specifically, we'll go over:

  • Installing the SDK 💻
  • Connecting to our Table Client and Creating a table 🔨
  • Defining our entity 🧾
  • Adding an entity ➕
  • Performing Transactional Batch Operations 💰
  • Querying our Table ❓
  • Deleting an entity ❌

Let's dive into it!

Installing the SDK 💻

Installing the SDK is pretty simple. We can do so by running the following dotnet command:

dotnet add package Azure.Data.Tables

If you prefer using a UI to install the NuGet packages, we can do so by right-clicking our C# Project in Visual Studio, click on Manage NuGet packages and search for the Azure.Data.Tables package:

Connecting to our Table Client and Creating a table 🔨

The SDK provides us with two clients to interact with the service. A TableServiceClient is used for interacting with our table at the account lelvel.

We do this for creating tables, setting access policies etc.

We can also use a TableClient. This is used for performing operations on our entities. We can also use the TableClient to create tables like so:

TableClient tableClient = new TableClient(config["StorageConnection"], "Customers");
            await tableClient.CreateIfNotExistsAsync();

To create our Table Client, I'm passing in my storage connection string from Azure and the name of the table I want to interact with. On the following line, we create the table if it doesn't exist.

To get out Storage Connection string, we can do so from our Cosmos DB account under Connection String:

When we run this code for the first time, we can see that the table has been created in our Data Explorer:

Defining our entity 🧾

In Table Storage, we create entities in our table that require a Partition Key and a Row Key. The combination of these need to be unique within our table.

Entities have a set of properties and strongly-typed entities need to extend from the ITableEntity interface, which expose Partition Key, Row Key, ETag and Timestamp properties. ETag and Timestamp will be generated by Cosmos DB, so we don't need to set these.

For this tutorial, I'm going to use the above mentioned properties along with two string properties (Email and PhoneNumber) to make up a CustomerEntity type.

public class CustomerEntity : ITableEntity
{
    public string PartitionKey { get; set ; }
    public string RowKey { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public ETag ETag { get; set; }
}

Adding an entity ➕

To add a new entity into our table, we need to instantiate it and simply call the .AddEntityAsync() method to insert it:

CustomerEntity customerEntity = new CustomerEntity()
{
     PartitionKey = "Velida",
     RowKey = "Will",
     PhoneNumber = "0123456789",
     Email = "[email protected]"
};

await tableClient.AddEntityAsync(customerEntity);

Heading back into our Customers table in Cosmos DB, we can see that the entity was successfully inserted.

Performing Transactional Batch Operations 💰

The table service allows us to make multiple operations within a single batch request.

Transaction are 'all-or-nothing', meaning that if one operation in our batch fails, they will all fail! Transactions can perform a mixture of create, delete, update and upsert operations.

Just take note that all operations within a transaction needs to target the same partition key.

In the code snippet below, I'm creating a list of CustomerEntity objects that I'm going to insert into my table as a batch create operation.

We then create a new List of type TableTransactionAction and add the list of entities we want to include in the batch operation to it. We then use the .SubmitTransactionAsync() method to submit the batch operation:

string partitionKey = "Velida";
List<CustomerEntity> familyList = new List<CustomerEntity>
{
      new CustomerEntity
      {
            PartitionKey = partitionKey,
            RowKey = "Don",
            PhoneNumber = "0987612345"
      },
      new CustomerEntity
      {
            PartitionKey = partitionKey,
            RowKey = "Jane",
            PhoneNumber = "0987612345"
      },
      new CustomerEntity
      {
            PartitionKey = partitionKey,
            RowKey = "Jan",
            PhoneNumber = "0987601298",
            Email = "[email protected]"
      }
};

List<TableTransactionAction> addFamilyBatch = new List<TableTransactionAction>();

addFamilyBatch.AddRange(familyList.Select(f => new TableTransactionAction(TableTransactionActionType.Add, f)));

Response<IReadOnlyList<Response>> response = await tableClient.SubmitTransactionAsync(addFamilyBatch);

Heading back into our table, we can see the entities have been successfully inserted into our Table:

Querying our Table ❓

We can query our data in Table storage in a couple of ways. The code snippet below uses a OData expression.

Working with OData query filters can be a pain, but the SDK provides a helper library that makes it a bit easier. Using .CreateQueryFilter(), we can write our OData query like so:

Pageable<TableEntity> oDataQueryEntities = tableClient.Query<TableEntity>(filter: TableClient.CreateQueryFilter($"PartitionKey eq {partitionKey}"));

foreach (TableEntity entity in oDataQueryEntities)
{
    Console.WriteLine($"{entity.GetString("PartitionKey")}:{entity.GetString("RowKey")}");
}

Running this code, we can see a concatenation of our entities PartitionKeys and RowKeys like so:

We can also use LINQ expressions to query our Table. Here, I'm using a LINQ query to retrieve a CustomerEntity with a RowKey value of "Will":

Pageable<CustomerEntity> linqEntities = tableClient.Query<CustomerEntity>(customer => customer.RowKey == "Will");

foreach (var entity in linqEntities)
{
     Console.WriteLine($"{entity.RowKey} {entity.PartitionKey}");
}

When this code runs, we can see our entity printed out to us:

Deleting an entity ❌

Deleting entities from our table is just a simple .DeleteEntityAsync() call. All we need to do is pass in our ParitionKey and RowKey value like so:

await tableClient.DeleteEntityAsync(partitionKey, "Will");

Checking our table, we can see that our entity has been successfully deleted:

Wrapping up

Hopefully after reading this article, you understand that working with the Azure.Data.Tables SDK is pretty straightforward. I like the approach that the Azure SDK team is taking with making it's SDKs more consistent with each other.

Using the Azure.Data.Tables SDK, we can build applications that work with both Azure Cosmos DB table storage and regular table storage, so if you find that you're building an application using regular Table Storage and you're struggling with scale, you can switch to Cosmos DB without any code changes!

If you want to learn more about the Azure.Data.Tables library, check out this blog post and this GitHub repo!

Hopefully you found this article useful! As always, if you have any questions, feel free to comment below or ask me on Twitter!

Happy Coding! 💻👨‍💻👩‍💻

24