In this article, we are going to learn about Azure Table Storage in depth.

We’ll start by discussing the basic Azure Table Storage concepts and then see how to create an Azure Storage Account from the portal. After that, we’ll learn how to create an ASP.NET Core Web API for performing CRUD operations on NoSQL data with the Table Storage in the back-end. 

To download the source code for this article, you can visit our Azure Table Storage with ASP.NET Core repository.

So, let’s start.

Azure Table Storage – Intro

Azure Table storage is a cloud-based NoSQL datastore. We can use it to store large amounts of structured, non-relational data. Azure Table offers a schemaless design, which provides us with a convenient way to store a collection of entities. An entity can contain a set of properties, and each property can contain a name-value pair. Azure Table is a lightweight, easy to manage service which makes it a good choice for people who are just getting started with NoSQL data and want to leverage cloud data services.

For integrating Azure Table Storage into our applications, we can use the Azure Tables client library for .NET NuGet package.  This library can seamlessly work with either Azure Table Storage or Azure Cosmos DB Table Service endpoints without any code changes.    

Azure Table Storage vs Azure Cosmos DB Table API

Both the Azure Table Storage and Azure Cosmos DB Table API share the same table data model and even provide the same CRUD operations through their SDKs. That means we can use either service for achieving the same functionality, but the major difference is performance and scalability. 

Table Storage is a lightweight and much cheaper option which makes it a good choice when we’re just starting with small storage requirements. On the other hand, the Azure Cosmos DB Table API is a premium service that provides two-level indexes, global distribution, throughput-optimized tables, etc, and of course much more expensive. 

Since these services use the same data model and client libraries, it is possible to migrate from Table Storage to Cosmos DB Table Service with minimal changes, which offers greater flexibility for developers and architects.

Structure of an Azure Table Storage

An Azure Table Storage is organized into Storage Accounts, Tables, Entities, and Properties:

Accounts: All access to Azure Storage is done through a storage account. The storage account’s name acts as a namespace for accessing all storage types like Blobs, Queues, Tables, etc.

Table: A Storage account can have multiple tables. A Table consists of a collection of entities, but it doesn’t enforce a schema on the entities, which means a single table can contain entities that have different sets of properties. This gives us a flexible way of storing schemaless data.

Entity: An entity is a set of properties and can be compared to a row in a database. There is a size limit of 1 MB for an entity in Table Storage.

Properties: A property is a name-value pair and each entity can have up to 252 properties. There is a size limit of 64 KB for a property. 

System Properties

Apart from the user-defined properties, an entity will always have three system properties – PartitionKey, RowKey, and Timestamp. While we are responsible for managing the values of PartitionKey and RowKey, the storage manages the value of Timestamp and there is no option to modify it.

Within a table, entities are organized by partitions. A partition is a consecutive range of entities having the same PartitionKey value. The PartitionKey denotes the first part of an entity’s primary key and every operation on the entity needs to provide this value.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

The RowKey property denotes the second part of the Table’s primary key. This helps to uniquely identify an entity within a partition. So we need to provide the RowKey property as well as the PartitionKey for performing any operation on an entity.

A combination of PartitionKey and RowKey uniquely identifies an entity within a table. 

We can represent the structure of a Tables Storage this way:

azure table storage structure

Knowing the Storage Account name and table name, we can access the Azure Table Storage by the URL format: http://<storage account>.table.core.windows.net/<table>

Creating an Azure Table Storage

We have already explained the process of creating an Azure Storage Account in the Creating Azure Storage section of the Upload Files to Azure with .NET Core Web API and Blazor WebAssembly article. Following similar steps, we can create and configure a storage account:

azure table storage menu

For accessing the Tables section, we can use the Tables menu:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

azure table storage tables seection

Initially, the storage account will not have any tables in it, but we are going to create an API that will create a table and insert entities into it. Make sure to note the connection string as mentioned in the linked article as we are going to need that later for connecting to the storage account from the app.

Building an API that Connects with Azure Table Storage

Now let’s build an ASP.NET Core Web API that connects with Azure Table Storage and performs read and write operations. We are going to build an API to work with grocery store items. For that, we can create an ASP.NET Core Web API project. 

As discussed in the previous section, we’ll be using the Azure Tables client library for .NET for working with Azure Tables. 

So let’s add the Azure.Data.Tables NuGet package to our project: 

Install-Package Azure.Data.Tables

This library supports working with both Azure Table Storage and Azure Cosmos DB Table API. Cosmos DB provides several advanced features over Azure Table Storage but shares the same data model and operations. 

Next, let’s make sure to configure the connection string in the appsettings file:    

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
{
...
  },
  "StorageConnectionString": "DefaultEndpointsProtocol=https;AccountName=codemazestorage;AccountKey=UjBmo....;EndpointSuffix=core.windows.net",
  "AllowedHosts": "*"
}

Cool!

Defining the Entity

Now we need to create an Entity for storing data in the Azure Table Storage. Let’s define an entity for representing a grocery item. Note that If we need to store an entity in Azure Table Storage, we need to implement the ITableEntity interface:

public class GroceryItemEntity : ITableEntity
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public int Price { get; set; }
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public ETag ETag { get; set; }
}

While implementing the ITableEntity interface, we have to define the default properties PartitionKey, RowKey, Timestamp, and ETag in addition to our entity’s attributes.

As discussed earlier, while working with Azure Tables, we need to specify a partition key and a row key for our entity. For this example, let’s choose Category as the partition key and Id as the row key. Always choose these values in such a way that the partition key can partition our data into groups and the row key can identify an entity uniquely within a partition.

Building the Service

Now it’s time to build the storage service.

For that, first let’s create an ITableStorageService interface: 

public interface ITableStorageService
{
    Task<GroceryItemEntity> GetEntityAsync(string category, string id);
    Task<GroceryItemEntity> UpsertEntityAsync(GroceryItemEntity entity);
    Task DeleteEntityAsync(string category, string id);
}

After that, let’s create a TableStorageService class implementing the interface: 

public class TableStorageService : ITableStorageService
{
    private const string TableName = "Item";
    private readonly IConfiguration _configuration;

    public TableStorageService(IConfiguration configuration)
    {
        _configuration = configuration;
    }
}

In the service, first, let’s create a GetTableClient() method to connect to Azure Storage and access the table inside it. This will create a table if it does not exist and return the reference to the TableClient object:  

private async Task<TableClient> GetTableClient()
{
    var serviceClient = new TableServiceClient(_configuration["StorageConnectionString"]);

    var tableClient = serviceClient.GetTableClient(TableName);
    await tableClient.CreateIfNotExistsAsync();
    return tableClient;
}

After that, let’s create the methods for reading, updating, and deleting entities:

public async Task<GroceryItemEntity> GetEntityAsync(string category, string id)
{
    var tableClient = await GetTableClient();
    return await tableClient.GetEntityAsync<GroceryItemEntity>(category, id);
}

public async Task<GroceryItemEntity> UpsertEntityAsync(GroceryItemEntity entity)
{
    var tableClient = await GetTableClient();
    await tableClient.UpsertEntityAsync(entity);
    return entity;
}

public async Task DeleteEntityAsync(string category, string id)
{
    var tableClient = await GetTableClient();
    await tableClient.DeleteEntityAsync(category, id);            
}

In the GetEntityAsync() method, we retrieve an entity by passing its category and Id which are the partition key and row key respectively. 

Similarly, the UpsertEntityAsync() method will perform the Upsert operation. This is a special operation that inserts the given entity into a table if it does not exist and replaces the contents in case the entity already exists.

Finally, we have the DeleteEntityAsync() method that will perform a delete operation on the entity. We have to specify the partition key and row key for deleting an entity as well.

With that, our service is ready. Next, let’s create an API Controller and consume this service.

Creating the Controller

Now let’s define an ItemsController class:

public class ItemsController : ControllerBase
{
    private readonly ITableStorageService _storageService;

    public ItemsController(ITableStorageService storageService)
    {
        _storageService = storageService ?? throw new ArgumentNullException(nameof(storageService));
    }
}

Notice that we have injected the TableStorageService service into the controller. Next, let’s create the GET, POST, PUT and DELETE endpoints:

[HttpGet]
[ActionName(nameof(GetAsync))]
public async Task<IActionResult> GetAsync([FromQuery] string category, string id)
{
    return Ok(await _storageService.GetEntityAsync(category, id));
}

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] GroceryItemEntity entity)
{
    entity.PartitionKey = entity.Category;

    string Id = Guid.NewGuid().ToString();
    entity.Id = Id;
    entity.RowKey = Id;

    var createdEntity = await _storageService.UpsertEntityAsync(entity);
    return CreatedAtAction(nameof(GetAsync), createdEntity);
}

[HttpPut]
public async Task<IActionResult> PutAsync([FromBody] GroceryItemEntity entity)
{
    entity.PartitionKey = entity.Category;
    entity.RowKey = entity.Id;

    await _storageService.UpsertEntityAsync(entity);
    return NoContent();
}

[HttpDelete]
public async Task<IActionResult> DeleteAsync([FromQuery] string category, string id)
{
    await _storageService.DeleteEntityAsync(category, id);
    return NoContent();
}

The GetAsync() endpoint is pretty straightforward and just calls the GetEntityAsync() method of the service by passing the category and Id.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Both the PostAsync() and PutAsync() endpoints first set the PartitionKey and RowKey property on the Entity and then call the UpsertEntityAsync() method.

The DeleteAsync() endpoint calls the DeleteEntityAsync() method by passing the category and Id.

Finally, let’s configure the dependency injection for the service in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
...
    services.AddScoped<ITableStorageService, TableStorageService>();
}

With this, the Web API project is ready. The next step is deploying it to an Azure API App. 

Deploying the API to Azure

For the ease of testing the API endpoints, let’s modify the Configure() method of the Startup class to enable swagger for the hosted version:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GroceryStoreAPI v1"));
    app.UseHttpsRedirection();

...
}

We have explained the process of publishing an ASP.NET Core Web API into Azure in the Deploying ASP.NET Core Web API to Azure API Apps article. By following the steps mentioned in that article, we can create an Azure API app and deploy our API into it:

api deployed

Excellent!

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Our API app is now ready in Azure:

swagger ui

Now let’s proceed to test the endpoints.

Testing the API

We’ll start by inserting a new grocery item using the POST endpoint: 

POST request

Cool!

The operation is successful and we have inserted a new grocery item into the Azure Table Storage.

Next, let’s try to fetch the entity using the GET endpoint. Remember that we need to provide the Partition Key and Row Key for that, which is the category and Id properties respectively for this example:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

GET request

This will return the entity from Azure Table Storage.

Excellent!

Now, let’s try to update the price property of the entity using the PUT endpoint:

PUT request

Once the update operation is completed, let’s fetch the entity once again using the GET endpoint to verify the changes:

GET after Update

Great!

We can see that the price is updated.

Finally, let’s delete the entity using the DELETE endpoint:

DELETE request

We can verify that the entity is deleted by performing the GET operation once again:

GET after Delete

This time we receive a 204 No Content response with an empty body which indicates that the entity is not found in the Azure Table Storage.

Conclusion

We can represent the architecture of this example with a simple diagram:

architecture

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Users can access the Azure API App and work with NoSQL data. The API App connects with the Azure Table Storage in the back-end for storing the data.

We have discussed the following topics in this article:

  • An introduction to Azure Table Storage, its structure, and how it compares with the Azure Cosmos DB Table API
  • Creating an Azure Table Storage from Azure Portal.
  • Building an ASP.NET Core Web API to connect with Azure Table Storage and work with NoSQL data

Until the next article,

All the best.