Introduction
This is the first article I have written in a very
long time. I wanted to have a generic API to Azure Table Store. Since I could
not find one here is my first attempt at writing one based on the sample code
from http://www.windowsazure.com/en-us/develop/net/how-to-guides/table-services/.
Using the code
I
wanted to simplify my code such that details of where the table store is and
the connections details are hidden. I also wanted a simply generic set of methods
that accept classes inheriting from TableEntity to do simple CRUD operations.
Below is how such a DTO object looks like
public class SomeDataItem : TableEntity
{
public string String1 { get; set; }
public string String2 { get; set; }
public SomeDataItem()
{
}
public SomeDataItem(string PartitionKey, string RowKey)
{
this.PartitionKey = PartitionKey;
this.RowKey = RowKey;
}
}
The best way to model how an API should look is from a unit test. The code below is more of an integration test but shows how I want the TableStorage class to be
used. It starts by inserting an object into the table store and then carries out CRUD operations. Notice that the only thing the code passes into the class is the TableName, all
the rest is handled in the App.config.
[TestMethod]
public void TableStorageTest()
{
string TableName = "MyTable";
TableStorage t = new TableStorage(TableName);
string partitionKey = "partitionkey1";
string rowKey = "rowkey1";
SomeDataItem data = new SomeDataItem(partitionKey ,rowKey);
data.String1 = "test.txt";
data.String2 = "contents";
t.Insert<SomeDataItem>(data);
SomeDataItem data2 = t.GetSingle<SomeDataItem>(partitionKey, rowKey);
Assert.IsTrue(data.String2 == data2.String2);
Assert.IsTrue(data.String1 == data2.String1);
List<SomeDataItem> listTableFileItem = t.GetAll<SomeDataItem>(partitionKey);
Assert.IsTrue(listTableFileItem.Count == 1);
data2.String2 = "NewContents";
t.Replace<SomeDataItem>(data2.PartitionKey, data2.RowKey, data2, true);
data = t.GetSingle<SomeDataItem>(partitionKey, rowKey);
Assert.IsTrue(data.String2 == data2.String2);
Assert.IsTrue(data.String1 == data2.String1);
t.DeleteEntry<SomeDataItem>(partitionKey, rowKey, data2);
t.DeleteTable();
}
Below
is how the TableStorage class looks. As can be seen I have simply wrapped the
sample code in some generic classes.
using System;
using System.Collections.Generic;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
namespace DataStore
{
/// <summary>
/// Simple Table storage generic helper class
/// </summary>
public class TableStorage
{
/// <summary>
/// Handle to the Azure table.
/// </summary>
/// <value>
/// The table.
/// </value>
private CloudTable table { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TableStorage"/> class.
/// </summary>
/// <param name="TableName">Name of the table.</param>
public TableStorage(string TableName)
{
// Retrieve the storage account from the connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
CloudConfigurationManager.GetSetting("StorageConnectionString"));
// Create the table client.
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
// Create the table if it doesn't exist.
this.table = tableClient.GetTableReference(TableName);
table.CreateIfNotExists();
}
/// <summary>
/// Inserts the specified data.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="data">The data.</param>
public void Insert<T>(T data) where T : TableEntity
{
// Create the TableOperation that inserts the customer entity.
TableOperation insertOperation = TableOperation.Insert(data);
// Execute the insert operation.
this.table.Execute(insertOperation);
}
/// <summary>
/// Inserts a list of table entries as a batch.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="data">The data.</param>
public void InsertBatch<T>(List<T> data) where T : TableEntity
{
// Create the batch operation.
TableBatchOperation batchOperation = new TableBatchOperation();
// Add both customer entities to the batch insert operation.
foreach (TableEntity d in data)
{
batchOperation.Insert(d);
}
// Execute the batch operation.
table.ExecuteBatch(batchOperation);
}
/// <summary>
/// Gets all data corresponding to a partition key.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="PartitionKey">The partition key.</param>
/// <returns>A list of T that has the corresponding partion key</returns>
public List<T>GetAll<T>(string PartitionKey) where T : TableEntity
{
// Construct the query operation for all customer entities where PartitionKey="Smith".
TableQuery<SomeDataItem> query = new TableQuery<SomeDataItem>().Where(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, PartitionKey));
List<T> Results = new List<T>();
// Print the fields for each customer.
foreach(SomeDataItem entity in table.ExecuteQuery(query))
{
Results.Add(entity as T);
//Console.WriteLine("{0}, {1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey,
// entity.Email, entity.PhoneNumber);
}
return Results;
}
/// <summary>
/// Gets the single.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="PartitionKey">The partition key.</param>
/// <param name="RowKey">The row key.</param>
/// <returns></returns>
public T GetSingle<T>(string PartitionKey, string RowKey ) where T : TableEntity
{
// Create a retrieve operation that takes a customer entity.
TableOperation retrieveOperation = TableOperation.Retrieve<T>(PartitionKey, RowKey);
// Execute the retrieve operation.
TableResult retrievedResult = table.Execute(retrieveOperation);
T result = null;
// Print the phone number of the result.
if (retrievedResult.Result != null)
{
result = retrievedResult.Result as T;
}
return result;
}
/// <summary>
/// Replaces the specified partition key.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="PartitionKey">The partition key.</param>
/// <param name="RowKey">The row key.</param>
/// <param name="ReplacementData">The replacement data.</param>
/// <param name="InsertOrReplace">The insert O replace.</param>
public void Replace<T>(string PartitionKey, string RowKey,
T ReplacementData, Boolean InsertOrReplace) where T : TableEntity
{
// Create a retrieve operation that takes a customer entity.
TableOperation retrieveOperation = TableOperation.Retrieve<T>(PartitionKey, RowKey);
// Execute the operation.
TableResult retrievedResult = table.Execute(retrieveOperation);
// Assign the result to a CustomerEntity object.
T updateEntity = retrievedResult.Result as T;
if (updateEntity != null)
{
ReplacementData.PartitionKey = updateEntity.PartitionKey;
ReplacementData.RowKey = updateEntity.RowKey;
// Create the InsertOrReplace TableOperation
TableOperation updateOperation;
if (InsertOrReplace)
{
updateOperation = TableOperation.InsertOrReplace(ReplacementData);
}
else
{
updateOperation = TableOperation.Replace(ReplacementData);
}
// Execute the operation.
table.Execute(updateOperation);
Console.WriteLine("Entity updated.");
}
else
Console.WriteLine("Entity could not be retrieved.");
}
/// <summary>
/// Deletes the entry.
/// </summary>
/// <typeparam name="T">DTO that inherits from TableEntity</typeparam>
/// <param name="PartitionKey">The partition key.</param>
/// <param name="RowKey">The row key.</param>
/// <param name="ReplacementData">The replacement data.</param>
public void DeleteEntry<T>(string PartitionKey, string RowKey, T ReplacementData) where T : TableEntity
{
// Create a retrieve operation that expects a customer entity.
TableOperation retrieveOperation = TableOperation.Retrieve<T>(PartitionKey, RowKey);
// Execute the operation.
TableResult retrievedResult = table.Execute(retrieveOperation);
// Assign the result to a CustomerEntity.
T deleteEntity = retrievedResult.Result as T;
// Create the Delete TableOperation.
if (deleteEntity != null)
{
TableOperation deleteOperation = TableOperation.Delete(deleteEntity);
// Execute the operation.
table.Execute(deleteOperation);
Console.WriteLine("Entity deleted.");
}
else
Console.WriteLine("Could not retrieve the entity.");
}
/// <summary>
/// Deletes the table.
/// </summary>
public void DeleteTable()
{
// Delete the table it if exists.
table.DeleteIfExists();
}
}
}
Conclusion
I am sure there are many ways to improve this. For example I could make the class Asynchronous, I could handle the transfer of large data with some progress
information etc. What I wanted to demonstrate is how to use generic functions to simplify CRUD Table operations. I hope this is useful.