Introduction
Table Storage is a key-value store that uses a partition key to help with scale out distribution of data and a row key for unique access to a particular entry within a partition. Together, these keys are used to uniquely identify a record in the account.
Accessing the account
First, we will need to add the following to our configuration file (app.config if it’s a console application, web.config if it’s a web application):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<configuration> | |
<appSettings> | |
<add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=<your account name>;AccountKey=<your account key>" /> | |
</appSettings> | |
</configuration> |
Then, we will access our account with the following code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.ServiceRuntime; | |
using Microsoft.WindowsAzure.Storage; | |
public static CloudStorageAccount GetAccount() | |
{ | |
const string configName = "StorageConnectionString"; | |
string value = RoleEnvironment.GetConfigurationSettingValue(configName); | |
return CloudStorageAccount.Parse(value); | |
} |
Creating a Table
To create a Table, we can use the CreateIfNotExists
function, as shown in the sample below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public CloudTable CreateTable(CloudStorageAccount storageAccount) | |
{ | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("orders"); | |
table.CreateIfNotExists(); | |
return table; | |
} |
Inserting records
To insert a record we will first need a class that inherits from TableEntity
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public class OrderEntity : TableEntity | |
{ | |
public OrderEntity(string customerName, string orderDate) | |
{ | |
PartitionKey = customerName; | |
RowKey = orderDate; | |
} | |
public OrderEntity() | |
{ | |
} | |
public string OrderNumber { get; set; } | |
public DateTime OrderDate { get; set; } | |
public DateTime ShippedDate { get; set; } | |
public string Status { get; set; } | |
} |
Then, we create a new OrderEntity
, fill the values, and execute an Insert operation on the Table:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public void InsertOrder(CloudStorageAccount storageAccount) | |
{ | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("orders"); | |
OrderEntity newOrder = new OrderEntity("Smith", "20150410"); | |
newOrder.OrderNumber = "1"; | |
newOrder.OrderDate = Convert.ToDateTime("20150410"); | |
newOrder.ShippedDate = Convert.ToDateTime("20150411"); | |
newOrder.Status = "shipped"; | |
TableOperation insertOperation = TableOperation.Insert(newOrder); | |
table.Execute(insertOperation); | |
} |
Inserting multiple records
We can also insert multiple records in a batch transaction, with the following limitations:
- All operations must take place on the same partition.
- The batch can include up to 100 operations.
- The batch payload cannot be greater than 4 MB.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public void InsertOrders(CloudStorageAccount storageAccount) | |
{ | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("orders"); | |
TableBatchOperation batchOperation = new TableBatchOperation(); | |
OrderEntity newOrder1 = new OrderEntity("Smith", "20150410"); | |
newOrder1.OrderNumber = "1"; | |
newOrder1.OrderDate = Convert.ToDateTime("20150410"); | |
newOrder1.ShippedDate = Convert.ToDateTime("20150411"); | |
newOrder1.Status = "shipped"; | |
OrderEntity newOrder2 = new OrderEntity("Jones", "20150413"); | |
newOrder2.OrderNumber = "2"; | |
newOrder2.OrderDate = Convert.ToDateTime("20150413"); | |
newOrder2.ShippedDate = Convert.ToDateTime("19000101"); | |
newOrder2.Status = "pending"; | |
batchOperation.Insert(newOrder1); | |
batchOperation.Insert(newOrder2); | |
table.ExecuteBatch(batchOperation); | |
} |
Updating records
To update a record, we first get the record, then we can create an InsertOrReplace
operation:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public void ShipOrder(CloudStorageAccount storageAccount, string partitionKey, string rowKey) | |
{ | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("orders"); | |
TableOperation retrieveOperation = TableOperation.Retrieve<OrderEntity>(partitionKey, rowKey); | |
TableResult retrievedResult = table.Execute(retrieveOperation); | |
OrderEntity toUpdate = (OrderEntity) retrievedResult.Result; | |
if (toUpdate != null) | |
{ | |
toUpdate.Status = "shipped"; | |
toUpdate.ShippedDate = Convert.ToDateTime("20150417"); | |
TableOperation insertOrReplaceOperation = TableOperation.InsertOrReplace(toUpdate); | |
table.Execute(insertOrReplaceOperation); | |
} | |
} |
Deleting a record
To delete a record, we must first retrieve the record (as shown in the previous code sample) and the create a Delete
operation:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public void DeleteOrder(CloudTable table, OrderEntity toDelete) | |
{ | |
TableOperation deleteOperation = TableOperation.Delete(toDelete); | |
table.Execute(deleteOperation); | |
} |
Querying Azure Table Storage
In the first versions of the Azure Storage library (versions 1.x), the experience was much like that of an Entity Repository, in the sense that we had a Context and lots of LINQ calls. Although it was easy to understand, it was really difficult to determine exactly when the query was done to the service. And it was pretty upsetting to be limited only to “Where”, “First” and “Select” LINQ functions.
Since version 2.0, we have a TableQuery
object available, which gives us more control over our query, and we can better understand what’s happening.
Querying for an entity
To query for an entity, we will need the partition key and the row key.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public object GetEntity(string partitionKey, string rowKey) | |
{ | |
CloudStorageAccount account = GetAccount(); | |
CloudTableClient cloudTableClient = account.CreateCloudTableClient(); | |
CloudTable table = cloudTableClient.GetTableReference("YourTableName"); | |
TableOperation retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); | |
TableResult retrievedResult = table.Execute(retrieveOperation); | |
return retrievedResult.Result; | |
} |
Applying Filters
Table Storage supports OData, which allows us to make some basic querying: filter, top, select, orderby and a few others. The Storage Client library exposes then through the TableQuery
class.
This allows us, for example, to get all records in a partition:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
public void GetFromPartition(CloudStorageAccount storageAccount, string partitionKey) | |
{ | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("orders"); | |
TableQuery<OrderEntity> query = new TableQuery<OrderEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparison.Equal, partitionKey)); | |
foreach (OrderEntity entity in table.ExecuteQuery(query)) | |
{ | |
// Do something | |
} | |
} |
There are two important elements here: GenerateFilterCondition
and QueryComparisons
.
There are some variations to the GenerateFilterCondition
function, according to the data type, being this one the one used for strings.
GenerateFilterConditionForGuid
GenerateFilterConditionForLong
GenerateFilterConditionForDouble
GenerateFilterConditionForInt
GenerateFilterConditionForDate
GenerateFilterConditionForBool
GenerateFilterConditionForBinary
And the available options for QueryComparisons
are:
QueryComparisons.Equal
QueryComparisons.NotEqual
QueryComparisons.GreaterThan
QueryComparisons.LessThan
QueryComparisons.GreaterThanOrEqual
QueryComparisons.LessThanOrEqual
If we need to combine different filters, we use the TableQuery.CombineFilters()
function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
string filter1 = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Argentina"); | |
string filter2 = TableQuery.GenerateFilterCondition("Name", QueryComparisons.Equal, "Guillermo"); | |
string combinedFilter = TableQuery.CombineFilters(filter1, TableOperators.And, filter2); | |
TableQuery query = new TableQuery().Where(combinedFilter); |
With the operators being:
TableOperators.And
TableOperators.Or
TableOperators.Not
Selects
The Select
function allows us to query only for the fields we need. This function can be really useful in the cases when we need to save network bandwidth, like when we are building a WinRT app.
For example, if we want to get only the names of our customers:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CloudTable customerTable = tableClient.GetTableReference("customers"); | |
TableQuery query = new TableQuery().Select(new string[] { "Name" }); | |
customerTable.ExecuteQuery(query); |
Take
If we need to limit the amount of results, we just use the Take
function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
query.Take(10); |
Combining all
Of course, we can combine all these functions together:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TableQuery query = new TableQuery() | |
.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Argentina")) | |
.Select(new string[] { "LastName", "Name" }) | |
.Take(5); |
Strongly typed queries
All the previous example functions return dynamic values, but we can also make queries that return the class we need, by using TableQuery<OrderEntity>
.
Happy coding!
Nice blog.
Note that there is also an extension library which makes updating and querying much easier:
Windows Azure Storage Extensions: https://github.com/dtretyakov/WindowsAzure
“Windows Azure Storage Extensions is a .NET library aimed at managing and querying entities from Azure Storage Tables.”
LikeLiked by 1 person