With the release of Azure, ScrumWall needed to be updated from the sample data access code to the new API.
Cloud Storage Account
The first change that we made was the addition of CloudStorageAccount to handle access to storage.
Instantiating a storage account requires that a configuration setting publisher has been set. We do this with the SetConfigurationSettingPublisher method. It takes a single parameter, which is a wrapper delegate for the configuration reading and handling of the configuration change event. After setting the configuration provider, a call to the FromConfigurationSetting method, returns a new CloudStorageAccount instance using settings in the configuration file.
Cloud Table Client
With the storage account created, we now had a much simpler time accessing the data. From the account we can create a CloudTableClient instance.
The client allows us to create and delete tables without having to worry about catching exceptions with the methods CreateTableIfNotExist and DeleteTableIfExist. For example:
CloudStorageAccount account =
CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
CloudTableClient _client = account.CreateCloudTableClient();
_client.RetryPolicy = RetryPolicies.Retry(6, TimeSpan.FromSeconds(10));
_client.CreateTableIfNotExist(TableName);
TableServiceContext
In addition, the client has a nice “make your life easier” method called GetDataServiceContext. This creates a TableServiceContext object, removing the need for us to have a custom service context:
TableServiceContext _serviceContext = _client.GetDataServiceContext();
Once we had the account and service context sorted, our next step was to simplify our existing queries to take advantage of the service context and Table Service query extension method.
private IEnumerable<ProjectsByUserTable>
GetProjectByUser(Expression<Func<ProjectsByUserTable, bool>> predicate)
{
return _serviceContext
.CreateQuery<ProjectsByUserTable>(ScrumwallContext.ProjectsByUserTableName)
.Where(predicate)
.AsTableServiceQuery();
}
By using the service context to create a query for the desired table, we could then use standard LINQ to do the actual filtering. The final step of casting to a CloudTableQuery means that the specified retry behaviour is used when the query is enumerated.
TableServiceEntity
In the early days of Azure an entity within a table was represented by the base class TableStorageEntity. This has been subsequently changed to TableServiceEntity. To create a representation of a table storage entity, your class must now derive from this class. Any additional properties you want for the entity can be added to the class.
In ScrumWall we have numerous entities that we interact with and each requires similar data access code. We therefore abstracted all of this away into an abstract base class which handles connecting to Windows Azure and performs the appropriate data operations on the correct tables. Here is an example of what such a class might look like:
public abstract class DataSource<T> where T : TableServiceEntity
{
protected abstract string TableName { get; }
protected TableServiceContext _serviceContext;
private CloudTableClient _client;
public DataSource()
{
CloudStorageAccount account =
CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
_client = account.CreateCloudTableClient();
_client.RetryPolicy = RetryPolicies.Retry(6, TimeSpan.FromSeconds(10));
_serviceContext = _client.GetDataServiceContext();
}
protected IQueryable<T> GetEntity(Expression<Func<T, bool>> predicate)
{
var query = _serviceContext.CreateQuery<T>(TableName)
.Where(predicate)
.AsTableServiceQuery();
return query;
}
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="note">The entity.</param>
public virtual void Update(T entity)
{
_serviceContext.UpdateObject(entity);
_serviceContext.SaveChanges();
}
//Other methods elided e.g. Insert, Delete etc.
}
The DataSource abstract class only allows types that implement TableServiceEntity. Derived classes must specify the entity class as well as the actual table name within Windows Azure. When they are instantiated, a connection is established in the constructor using code described earlier. The DataSource class also contains all the boilerplate-type code to update, delete and read from the entities.
Derived classes can use the base class’s CRUD (Create, Read, Update and Delete) functionality while also adding their own class specific implementations. For example:
public class ProjectEntityDataSource : DataSource<ProjectEntity>
{
public ProjectEntity GetSingleProject(string projectId)
{
return GetEntity(c => c.PartitionKey == projectId)
.FirstOrDefault();
}
//rest of class elided
}
Summary
Following the release of Windows Azure, we had to update our Table Storage implementation accordingly. CloudStorageAccount gives us the ability to specify the global configuration provider for the storage account and also create an instance using settings specified in the configuration file. The storage account returns a new instance of CloudTableClient, which provides access to the Windows Azure Table service. Using the table client, we can create, delete and enumerate tables. The table client can also create a new instance of TableServiceContext. The service context allows us to perform standard data operations against a specified entity e.g. querying. Our custom entities are created by deriving from TableServiceEntity and adding our own properties.
We also refactored the most commonly used setup code away from each entity specific implementation into an abstract base class which aided in usability.
The new Windows Azure Table Storage API really simplifies interaction with tables and entities and as such makes Windows Azure a truly compelling solution as a Cloud-based storage solution.