Chapter 6. Windows Azure Storage Part III – Tables

The Windows Azure Table service provides structured storage in the cloud. Windows Azure tables aren't relational database tables as seen in traditional databases. The Table service follows a simple yet highly flexible model of entities and properties. In the simplest of terms, tables contain entities, and entities have properties. The Table service is designed for massive scalability and availability, supporting billions of entities and terabytes of data. It's designed to support high volume, but smaller structured objects. For example, you can use the Table service to store user profiles and session information in high-volume Internet sites. But if you also want to store the photos of users, you should store the images in a Blob service and save the link to the photo in Table service.

There is no limit on the number of tables and entities you can create in a Table service. There is also no limit on the size of the tables in your account. Figure 6-1 illustrates the Azure Services Developer Portal page for the storage account and URL endpoint for the Table service.

Table service endpoint URL

Figure 6.1. Table service endpoint URL

Table Service Architecture

The Table service architecture consists of a four-level hierarchical structure: Account, Table, Entity, and Properties, as shown in Figure 6-2.

Table service architecture

Figure 6.2. Table service architecture

Your Windows Azure storage account is the entry point to the Table service via the REST API.

Windows Azure Storage Account

The Windows Azure storage account encompasses the Blob, Queue, and Table services. The URI scheme for accessing the Table service via a storage account is

<http|https>://<account name>.table.core.windows.net

where <account name> is the globally unique name you created for your storage account. For example, the Table service for the storage account that I created in chapter 4 can be referenced as

<http|https>://proazurestorage.table.core.windows.net

Table

A table is a container for storing data. Data is stored in tables as collection of entities. There can be any number of tables in an account in the Table service. A table stores entities and makes them available to applications via the REST API and .NET client-side libraries like ADO.NET Data Services and LINQ. The Table service supports only private access, which means you must have account privileges to access tables in the Table service.

You can access a table with the following URI

<http|https>://<accountname>.table.core.windows.net/Tables('<table name>')

where <table name> is the name of the table you want to access. You can access all the tables in an account with the following URI:

<http|https>://<account name>.table.core.windows.net/Tables

For example, if you create a table named userprofiles in the proazurestorage account, you can reference it using the following URI:

<http|https>://proazurestorage.table.core.windows.net/Tables('userprofiles')

The naming constraints on a table are as follows:[8]

  • Table names must be valid DNS names.

  • Table names must be unique within an account.

  • Table names must contain only alphanumeric characters.

  • Table names must begin with an alphabetical character.

  • Table names are case sensitive.

  • Table names can't be fewer than 3 or more than 63 characters in length.

If a table name or the URI violates the naming convention, the server returns an HTTP status code 400 (Bad request).

Entity

Entities are stored in tables. They're analogous to rows in a relational database table. There is no limit on the number of entities that can be stored in a table. You can retrieve all the entities in a table with the following URI

<http|https>://<account name>.table.core.windows.net/<table name>()

Where <table name> is the name of the table you want to access, and the parentheses instructs the Table service to retrieve the entities in the specified table.

Property

An entity consists of a set of name-value pairs called properties. Properties are analogous to columns in a relational database table. An entity must have three mandatory properties: PartitionKey, RowKey, and Timestamp. PartitionKey and RowKey are of string data type, and Timestamp is a read-only DateTime property maintained by the system. The combination of PartitionKey and RowKey uniquely identifies an entity. You must design PartitionKey and RowKey as part of your table design exercise.

The Table service organizes data into several storage nodes based on the entities' PartitionKey property values. Entities with same PartitionKey are stored on a single storage node. A partition is a collection of entities with the same PartitionKey. A RowKey uniquely identifies an entity within a partition.

The Table service provides a single index in which entity records are sorted first by PartitionKey and then by RowKey. All the entities in a single partition have the same PartitionKey, so you can safely assume that all the entities in a partition are lexically sorted by RowKey. Figure 6-3 illustrates the design of an example PartitionKey and RowKey for a table.

PartitionKey and RowKey

Figure 6.3. PartitionKey and RowKey

In Figure 6-3, imagine you're designing an event management web site. On your web site, the most dominant user query is "Give me today's events in my city." So, the application queries the Events table for all the events on a particular day sorted with the most recent event at the top. The example in Figure 6-3 illustrates the Events table with its PartitionKey, RowKey and EventName properties.

Because the most dominant query retrieves all the events from a city, as an architect of the system, you want the dominant query to execute on a single partition (or storage node) for maximum query performance. If a San Francisco user comes to the web site, the application retrieves all the San Francisco events from a single partition. As explained earlier, the Table service groups entities with the same PartitionKey on the same partition. To achieve the desired distribution of entities, you define City as the PartitionKey for the entity; doing so groups all the events in a particular city on a single partition, as shown in Figure 6-3.

The table spans three partitions: the events "Book Release" and "Party" in San Francisco are stored on Partition 1, and New York and Seattle events are stored on Partition 2 and Partition 3. respectively. The distribution of a table across multiple partitions is opaque to the application.

The next part of the dominant query involves sorting events by date and time. Remember that the RowKey uniquely identifies an entity within a partition, and the entities are sorted by RowKey within a partition. So, you want all the events in a city (or partition) sorted by date and time. To sort the entities in a partition by date and time, you define RowKey as a function of date and time. You can achieve this by subtracting (DateTime.MaxValue.TicksEventTime.Ticks), where EventTime is the date and time of the event.

The combination of PartitionKey and RowKey uniquely identifies an entity within a table, so you can't have a duplicate PartitionKey and RowKey pair. There can't be two events in the same city starting at the exact same date and time. To create a unique PartitionKey and RowKey pair, you can append the RowKey with a GUID or any unique identifier of your choice. Because there can be only one PartitionKey and one RowKey in an entity, you must concatenate strings to create a PartitionKey and RowKey for every table design. In the future, when the Table service supports multiple indexes and RowKeys, the partitioning and sorting design will be more refined.

If the dominant query was "Get me today's Book Releases (Event Type) across all the Cities." the PartitionKey would be a function of time and Event Type, because you would want all the events of the same type across all the cities partitioned together. But you would also have to consider the impact of the volume of data on the query. The previous examples assume the number of entities on each partition are low enough for the query to perform optimally. If there are millions of events in a particular city, you would further refine the PartitionKey to reduce the load on the query.

While you're designing PartitionKeys, consider a tradeoff between scalability and performance. Depending on the capacity requirements and usage volume of your application, the PartitionKey may play an important role in scalability and performance. Having more partitions distributed over multiple storage nodes makes the table more scalable, whereas narrowing entities on a single partition may yield better performance, assuming the number of entities on a partition is low enough for the query to perform optimally.

Tip

Design your PartitionKeys and RowKeys in an iterative manner. Stress- and performance-test your design for every iteration. Then, choose an optimum PartitionKey that satisfies your application's performance and scalability requirements.

Table 6-1 lists the supported data types for property values and their Common Language Runtime (CLR) counterparts.

Table 6.1. Property Value Data Types

Data Type

Corresponding CLR Data Type

Binary

byte[]

Boolean

bool

DateTime

DateTime

Double

Double

Guid

Guid

Int32

int or Int32

Int64

long or Int64

String

string

The following are some of the characteristics of and constraints on entities and properties:

  • Tables support flexible schema. This means a table can contain entities that have property values of different data types. For example, in a UserProfiles table, you can have an entity record representing a ZipCode property with an integer data type ("ZipCode", 94582) and another entity record with a string data type for the same property ("ZipCode", "CK45G").

  • An entity can contain at the most 255 properties (including the PartitionKey, RowKey, and Timestamp properties, which are mandatory).

  • The total size of an entity including all the property names and values can't exceed 1MB.

  • Timestamp is a read-only value maintained by the system.

  • PartitionKey and RowKey can't exceed 1KB in size each.

  • Property names can contain only alphanumeric characters and the underscore (_) character. The following characters aren't supported in property names: backslash (), forward slash (/), dash (-), number sign (#), and question mark (?).

REST API

The REST API for the Table service is available at the table and entity levels of the hierarchy. The Table service API is compatible with the ADO.NET Data Services REST API. The differences between the Table service API and the ADO.NET Data Services API are highlighted in the Table services API section of the Windows Azure SDK documentation.[9] In this section, you learn about the Table service REST API with specific examples. You also learn to interact with the Table service programmatically, using the .NET Client Library and the Storage Client libraries from the Windows Azure SDK. The REST API enables you to send HTTP messages to the Table service and its resources.

REST is an HTTP-based protocol; you specify the URI of the resource as well as the function you want to execute on the resource. Every REST call involves an HTTP request to the storage service and an HTTP response from the storage service. The programming examples in this section use the .NET Client library and/or Storage Client library to access the Table service. Both of them ultimately result in REST API calls to the Table service, but I used them to keep the examples at a conceptual level. You can choose to program the Table service directly using the REST API.

Note

ADO.NET Data Services provides a REST API for accessing any data service on the Web. You can find the specification for the ADO.NET Data Services at http://msdn.microsoft.com/en-us/library/cc668808.aspx.

Request

The following sections describe the Table service REST API's HTTP request components.

HTTP Verb

The HTTP verb represents the action or operation you can execute on the resource indicated in the URI. The Table service REST API supports the following verbs: GET, PUT, MERGE, POST, and DELETE. Each verb behaves differently when executed on a different resource.

Request URI

The request URI represents the URI of a resource you're interested in accessing or executing a function on. Example resources in the Table service include table and entity. An example URI to create a table named Events in an account named proazurestorage is

POST http://proazurestorage.table.core.windows.net/Tables

Note that unlike in the Blob and Queue services, the URI doesn't include the name of the table (Events). The request body includes the details of the table to be created. The HTTP verb POST instructs the service to create the table, and the request body points to the resource that needs to be created.

URI Parameters

Typically, URI parameters are the extra parameters you specify to fine-tune your operation execution. They may include the operation parameters or filter parameters for the results. In the Table service API, the URI parameters support the ADO.NET Data Service Framework query options $filter and $top, as described in the ADO.NET Data Service specification at http://msdn.microsoft.com/en-us/library/cc668809.aspx.

The $filter parameter retrieves only the tables and entities that match the filter criteria specified in the URI. The following URI shows a sample usage of the $filter parameter:

http://proazurestorage.table.core.windows.net/ProAzureReader()?
     $filter=PurchaseDate%20eq%20datetime'2009-06-20T00:00:00'

ProAzureReader is the name of the table, and ProAzureReader() retrieves all the entities from the table. The URI further applies a filter "PurchaseDate eq datetime'2009-06-20T00:00:00'" for restricting the number of returned entities.

The $top parameter retrieves only Top(n) number of tables or entities specified in the URI. The following URI shows a sample usage of the $top parameter:

http://proazurestorage.table.core.windows.net/ProAzureReader()?$top=3

Again, ProAzureReader is the name of the table, and ProAzureReader() retrieves all the entities from the table. The $top parameter instructs the Table service to retrieve only the top three entities from the table.

Request Headers

Request headers follow the standard HTTP 1.1 name-value pair format. Depending on the type of request, the header may contain security information, date time information, or instructions embedded as name-value pairs. In the Storage Service REST API, the request header must include the authorization information and a Coordinated Universal Time (UTC) timestamp for the request. The timestamp can be in the form of either an HTTP/HTTPS Date header or an x-ms-Date header.

The authorization header format is as follows

Authorization="[SharedKey|SharedKeyLite] <Account Name>:<Signature>"

Where SharedKey|SharedKeyLite is the authentication scheme, <Account Name> is the storage service account name, and <Signature> is a Hash-based Message Authentication Code (HMAC) of the request computed using the SHA256 algorithm and then encoded by using Base64 encoding.

To create the signature, follow these steps:

  1. Create the signature string for signing. The signature string for the Storage service request consists of the following format:

    VERB
    Content - MD5
    Content - Type
    Date
    CanonicalizedHeaders
    CanonicalizedResource
    VERB is the uppercase HTTP verb such as GET, PUT, and so on. Content – MD5 is the MD5 hash of the request content. CanonicalizedHeaders is the portion of the signature string created using a series of steps described in the "Authentication Schemes" section of the Windows Azure SDK documentation: http://msdn.microsoft.com/en-us/library/dd179428.aspx. CanonicalizedResource is the storage service resource in the request URI. The CanonicalizedResource string is also constructed using a series of steps described in the "Authentication Schemes" section of the Windows Azure SDK documentation.
  2. Use the System.Security.Cryptography.HMACSHA256.ComputeHash() method to compute the SHA256 HMAC encoded string.

  3. Use the System.Convert.ToBase64String() method to convert the encoded signature to Base64 format.

Listing 6-1 shows an example request header for an entity GET operation.

Example 6.1. Request Header

User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Sat, 20 Jun 2009 22:42:54 GMT
x-ms-version: 2009-04-14
Authorization: SharedKeyLite
    proazurestorage:qWuBFkungfapSPIAFsrxeQ+j1uVRHyMUyEPiVOC832A=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Host: proazurestorage.table.core.windows.net

In Listing 6-1, the request header consists of x-ms-date, x-ms-version, and Authorization values. The x-ms-date represents the UTC timestamp, and the x-ms-version specifies the version of the storage service API you're using. The header also specifies the version of the ADO.NET Data Service API. The Authorization SharedKey header value is used by the Storage service to authenticate and authorize the caller.

The Table service REST API also supports the HTTP 1.1 If-Match conditional header. The If-Match conditional header is a mandatory header sent by the ADO.NET Data Service API. For update, merge, and delete operations, the Table service compares the specified ETag value with the ETag value on the server. If they don't match, an HTTP 412 (PreCondition Failed) error is sent back in the response. You can force an unconditional update by specifying a wildcard (*) for the If-Match header in the request.

Request Body

The request body consists of the contents of the request operation. Some operations require a request body, and some don't. For example, the Create Table operation's request body consists of an ADO.NET entity in the form of an Atom feed, whereas the Query Table operation requires an empty request body. Atom is an application-level protocol for publishing and editing web resources[10] defined by the Internet Engineering Task Force (IETF). You see other request body examples later in this chapter.

Note

For more information about the Atom format in ADO.NET Data Services messages, visit the "Atom Format" section in the ADO.NET Data Services specification at http://msdn.microsoft.com/en-us/library/cc668811.aspx.

Response

The HTTP response of the Table service API typically includes the components described in the following sections.

Status Code

The status code is the HTTP status code that indicates the success or failure of the request. The most common status codes for the Table service API are 200 (OK), 201 (Created), 400 (BadRequest), 404 (NotFound), 409 (Conflict), and 412 (PreCondition Failed).

Response Headers

The response headers include all the standard HTTP 1.1 headers plus any operation-specific headers returned by the Table service. The x-ms-request-id response header uniquely identifies a request. Listing 6-2 shows an example response header for a Query Entities operation.

Example 6.2. Query Entities Response Header

HTTP/1.1 200 OK
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/atom+xml;charset=utf-8
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: a1eccc1c-8c1f-4fca-8ca9-69850684e553
Date: Sat, 20 Jun 2009 22:43:45 GMT

Response Body

The response body consists of values returned by the operation. These values are specific to each operation. For example, the Query Entity operation returns an ADO.NET entity set, which is in the form of an Atom feed. Listing 6-3 shows an example of the response body for the following entity query:

GET http://proazurestorage.table.core.windows.net/ProAzureReader()?$
    filter=PartitionKey%20eq%20'06202009'

The response consists of two entities.

Example 6.3. Query Entity Response Body

<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<feed xml:base=http://proazurestorage.table.core.windows.net/
xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <title type="text">ProAzureReader</title>
<id>http://proazurestorage.table.core.windows.net/ProAzureReader</id>
  <updated>2009-06-20T22:43:46Z</updated>
  <link rel="self" title="ProAzureReader" href="ProAzureReader" />
  <entry m:etag="W/&quot;datetime'2009-06-20T13%3A01%3A10.5846Z'&quot;">
    <id>http://proazurestorage.table.core.windows.net/ProAzureReader
(PartitionKey='06202009',RowKey='12521567980278019999')</id>
    <title type="text"></title>
    <updated>2009-06-20T22:43:46Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="ProAzureReader"
href="ProAzureReader(PartitionKey='06202009',RowKey='12521567980278019999')" />
    <category term="proazurestorage.ProAzureReader"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>06202009</d:PartitionKey>
        <d:RowKey>12521567980278019999</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2009-06-20T13:01:10.5846Z
</d:Timestamp>
        <d:City>mumbai</d:City>
        <d:Country>india</d:Country>
        <d:EntryDate m:type="Edm.DateTime">2009-06-20T12:59:32.198Z
</d:EntryDate>
        <d:Feedback>Good Book :). But don't write again.</d:Feedback>
        <d:PurchaseDate m:type="Edm.DateTime">2009-06-20T00:00:00Z
</d:PurchaseDate>
        <d:PurchaseLocation>web</d:PurchaseLocation>
        <d:PurchaseType>New</d:PurchaseType>
        <d:ReaderName>tredkar</d:ReaderName>
        <d:ReaderUrl></d:ReaderUrl>
        <d:State>maharashtra</d:State>
        <d:Zip>400028</d:Zip>
      </m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2009-06-20T11%3A40%3A24.834Z'&quot;">
    <id>http://proazurestorage.table.core.windows.net/ProAzureReader
(PartitionKey='06202009',RowKey='12521568028370519999')</id>
    <title type="text"></title>
    <updated>2009-06-20T22:43:46Z</updated>
    <author>
      <name />
</author>
    <link rel="edit" title="ProAzureReader"
href="ProAzureReader(PartitionKey='06202009',
RowKey='12521568028370519999')" />
    <category term="proazurestorage.ProAzureReader"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>06202009</d:PartitionKey>
        <d:RowKey>12521568028370519999_</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2009-06-20T11:40:24.834Z</d:Timestamp>
        <d:City></d:City>
        <d:Country></d:Country>
        <d:EntryDate m:type="Edm.DateTime">2009-06-20T11:39:22.948Z</d:EntryDate>
        <d:Feedback>Good Book :). But don't write again.</d:Feedback>
        <d:PurchaseDate m:type="Edm.DateTime">2009-06-20T00:00:00Z
</d:PurchaseDate>
        <d:PurchaseLocation></d:PurchaseLocation>
        <d:PurchaseType>New</d:PurchaseType>
        <d:ReaderName></d:ReaderName>
        <d:ReaderUrl></d:ReaderUrl>
        <d:State></d:State>
        <d:Zip></d:Zip>
      </m:properties>
    </content>
  </entry>
</feed>

Tip

To test the REST API, I recommend using the Fiddler Tool available at http://www.fiddler2.com/fiddler2/. In this book, I use this tool to trace client/server communications.

ADO.NET Data Services Library (.NET Client Library)

The Table service API provides a subset of the ADO.NET Data Service API, so you can use the ADO.NET Data Services client library to work with tables and entities in the Table service. The System.Data.Services.Client assembly consists of the ADO.NET Data Services and .NET Client library classes.

Note

For more information about the Table service's support for the ADO.NET Data Services .NET Client library, visit the latest Table services API MSDN documentation at http://msdn.microsoft.com/en-us/library/dd894032.aspx.

You don't have to use the ADO.NET Data Services library to interact with tables and entities in the Table service. You may choose to work directly at the REST API level by constructing REST messages on your own.

Note

For more information about the ADO.NET Data Services .NET client library, visit http://msdn.microsoft.com/en-us/library/cc668789.aspx.

If you're using .NET Client library, the Table service lets you use a subset of Language Integrated Queries (LINQ) to interact with tables and entities. For more information about LINQ support in the Table service, visit the "Summary of Table Service Functionality" (http://msdn.microsoft.com/en-us/library/dd135720.aspx) and "Writing LINQ Queries" http://msdn.microsoft.com/en-us/library/dd894039.aspx) sections in the Table service API Windows Azure SDK documentation.

In this book, I use some of the ADO.NET Data Services .NET client library constructs as an alternative to using the Table service's REST API directly.

Storage Client APIs

Even though the REST API and the operations in the REST API are easily readable, the API doesn't automatically create the client stubs like those created by WSDL-based web services. You have to create your own client API and stubs for REST API operations. This makes the client programming more complex and increases the barrier to entry for developers. To reduce this barrier to entry, the Windows Azure SDK team has created two client helper libraries: Microsoft.WindowsAzure.StorageClient from Windows Azure SDK, and the Storage Client code sample. Both libraries are used to invoke REST APIs of the Windows Azure Storage service. The Microsoft.WindowsAzure.StorageClient library abstracts this by providing a closed-source interface and therefore is less interesting to developers who want to see the inner workings of the method calls.

In the Windows Azure CTP version, a code sample named Storage Client became the choice of developers to call Storage services because the Microsoft.WindowsAzure.StorageClient wasn't available and also because it was open source and thus gave good insight into building your own storage client library. The Storage Client makes it easier to see the end-to-end method calls to the Storage service. In this chapter, I use the Storage Client code sample in the sample applications. I also cover the class structure and calling mechanisms in the Microsoft.WindowsAzure.StorageClient namespace.

In the following sections, I cover the table storage APIs from Microsoft.WindowsAzure.StorageClient and the Storage Client code sample.

Note

You don't have to use any of the StorageClient library to make REST calls to the Storage service; you can instead create your own client library. In order to keep the book conceptual, I use the StorageClient code sample helper library to interact with the Storage service throughout this book. The source code for StorageClient is available in the samples directory of Windows Azure SDK or from the Windows Azure code samples site (http://code.msdn.microsoft.com/windowsazuresamples).

Windows Azure StorageClient Table API

The Microsoft.WindowsAzure.StorageClient namespace consists of classes representing the entire blob hierarchy. Figure 6-4 illustrates the core classes for programming Table service applications.

Table class hierarchy

Figure 6.4. Table class hierarchy

As shown in Figure 6-4, five core classes are required for table operations. Table 6-2 lists these classes and describes each of them.

Tip

The Windows Azure StorageClient API is the recommended method for programming storage service applications. The API provides synchronous as well as asynchronous methods to interact with the Storage service REST APIs.

Table 6.2. Classes for the Table Service

Class Name

Description

CloudStorageAccount

A helper class for retrieving account information from the configuration file or creating an instance of the storage account object from account parameters.

CloudTableClient

A wrapper class to interact with the Table service. It has methods like CreateTable(), DeleteTable(), GetDataServiceContext(), and ListTables().

TableServiceContext

Inherits from the System.Data.Services.Client.DataServiceContext class. It adds additional authentication functionality required by the Table service.

TableServiceEntity

An abstract class representing an entity(row) in a table. It has the mandatory properties (PartitionKey, RowKey, and Timestamp) defined in it. You may inherit your entity class from this class and provide additional properties.

CloudTableQuery<TElement>

Can be used to work with continuation tokens in Table service. A continuation token is similar to the NextMarker property you saw in the Blob and Queue services. It's a pointer to the next object available that wasn't retrieved due to the limit set on the results retrieved either by the application or the Table service itself.

The steps for programming simple table applications with these table classes are as follows:

  1. Add the following using statement to your C# class :

    using Microsoft.WindowsAzure.StorageClient;
  2. Instantiate the CloudStorageAccount class from configuration file:

    CloudStorageAccount  storageAccountInfo =
    CloudStorageAccount.FromConfigurationSetting(configurationSettingName);

    Or, instantiate the CloudStorageAccount class using account information:

    CloudStorageAccount  storageAccountInfo = new CloudStorageAccount(new
    StorageCredentialsAccountAndKey(accountName, accountKey), new Uri(blob
    EndpointURI), new
    Uri(queueEndpointURI), new Uri(tableEndpointURI));
  3. Create an instance of CloudTableClient:

    CloudTableClient tableStorageType = storageAccountInfo. CreateCloudTableClient ();
  4. When you have an instance of the CloudTableClient class, you can execute operations on the table storage service as follows:

    Create Table

    tableStorageType.CreateTable(tableName);

    Delete Table

    tableStorageType.DeleteTable(tableName);

    Get Tables

    IEnumerable<string> tables = tableStorageType.ListTables();

StorageClient Table Service API

The StorageClient library provides helper classes for using the ADO.NET Data Services and .NET Client libraries. The StorageClient project consists of six important classes in the context of the Table service. Table 6-3 lists these classes.

Table 6.3. StorageClient Classes for the Table Service

Class Name

Description

StorageAccountInfo

A helper class for retrieving account information from the configuration file.

TableStorage

The entry point to the Table service StorageClient API. It has an AccountName property and table-level methods like CreateTable(), ListTables(), and DeleteTable()

TableStorageTable

A generic table class representing local instance of tables in the Table service.

TableStorageEntity

An abstract class representing an entity(row) in a table. It has the mandatory properties (PartitionKey, RowKey, and Timestamp) defined in it. You may inherit your entity class from this class and provide additional properties.

TableStorageDataServiceContext

Inherits from the System.Data.Services.Client.DataServiceContext class. It adds authentication functionality required by the Table service.

TableStorageDataServiceQuery<TElement>

Can be used to work with continuation tokens in the Table service.

Figure 6-5 illustrates the six classes listed in Table 6-3 and the relationships between them.

StorageClient classes for the Table service

Figure 6.5. StorageClient classes for the Table service

The following are some of the typical steps required to interact with the Table service using StorageClient classes:

  1. Add your account information in the <appSettings> section of your application configuration file (app.config or web.config):

    <add key = "AccountName" value="[Your Account Name]"/>
    <add key = "AccountSharedKey"
    value="[Your Account Shared Key from the Developer Portal]"/>

    To work with local storage, add "devstoreaccount1" as the AccountName and "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" as the shared key.

  2. Add the Table service endpoint URI in the appSettings section of your application configuration file:

    <add key="TableStorageEndpoint" value="http://table.core.windows.net"/>

    To work with local storage, add the URI of the local Table service resource, http://127.0.0.1:10002.

  3. Define a schema modeling your tables in the table storage. This includes defining an entity class (optionally inherited from TableStorageEntity class) and a DataServiceContext class (optionally inherited from TableStorageDataServiceContext class). For development storage, you must run the DevTableGen.exe tool on these classes to generate SQL Server database tables.

  4. Create an instance of the StorageAccountInfo class from the configuration information you entered in the configuration file:

    StorageAccountInfo account =
    StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
  5. Create tables in the Table service:

    TableStorage.CreateTablesFromModel
    (typeof(ProAzureReaderDataContext), accountInfo);

    ProAzureReaderDataContext is a custom class that inherits from the TableStorageDataServiceContext class.

  6. Create an instance of the TableStorage class based on the account object:

    TableStorage tableStorage = TableStorage.Create(account);
  7. Call the ListTables() method on the tableStorage object to get a list of all the tables in the account:

    tableStorage.ListTables();

In the next few sections, you learn how to call some of these functions at every level of the Table service hierarchy.

Example Table Model

The Table service is quite different from the Blob and Queue services because the tables you create in the Table service are custom and depend on the application's data storage requirements. Unlike Table service, the Queue and Blob services don't require custom schemas to be created. In this section you create a simple application with a one-table custom schema. The purpose of this exercise is to demonstrate a broader overview of the Table service's features.

The application you create is called Pro Azure Reader Tracker. (You can go to the Pro Azure Reader Tracker web site and provide feedback.) The application has only one table, called ProAzureReader. Figure 6-6 illustrates the ProAzureReader class representing the table schema (or the entity).

ProAzureReader schema

Figure 6.6. ProAzureReader schema

As shown in Figure 6-6, the ProAzureReader class inherits from the TableStorageEntity class from the StorageClient library. The TableStorageEntity class defined the mandatory entity properties required by the Table service: PartitionKey, RowKey, and Timestamp. The ProAzureReader class defines the properties required to capture reader information and feedback:

  • The Feedback property represents the reader's feedback.

  • The EntryDate property represents the data-entry date.

  • The PurchaseDate property represents the date the book was purchased by the reader.

  • The PurchaseLocation property represents the location where the book was purchased by the reader.

  • The PurchaseType property represents whether the purchase was a new or used book.

  • The rest of the properties represent user information including name, address, and personal URL.

The ProAzureReader class also creates the PartitionKey and RowKey for the entity record.

Figure 6-7 illustrates how the ProAzureReaderDataContext class inherits from the TableStorageDataServiceContext class, which in turn inherits from the System.Data.Services.Client.DataServiceContext class of the ADO.NET Data Services .NET client library.

Note

You can use the same programming model with the table classes from the Microsoft.WindowsAzure.StorageClient namespace.

ProAzureReaderDataContext class

Figure 6.7. ProAzureReaderDataContext class

The DataServiceContext class represents the runtime context of ADO.NET Data Services. The context is a client-side construct and maintains the client-side state of invocations for update management between the client and the service.

Finally, the ProAzureReaderDataSource class is a utility class used to bind the data with client-side controls. See Figure 6-8.

ProAzureReaderDataSource class

Figure 6.8. ProAzureReaderDataSource class

As illustrated in Figure 6-8, the ProAzureReaderDataSource class consists of methods for inserting an entity into the table and retrieving entities from the table.

To design the PartitionKey, first you need to find out the most dominant query in the application. The application lists all the feedback entered by readers on a particular day. When you go the web page, you see all the feedback entries for the day. As a result, the most dominant query in the application can be phrased as, "Get all the entities entered today." If you design the PartitionKey as the same as the entity's EntryDate property, all the entries with the same EntryDate are placed in the same partition by the Table service. This executes the query locally on the partition and yields better query performance.

The query should list the results sorted by time with the most recent entry at the top. To achieve this, the RowKey must be a function of EntryDate. Listing 6-4 shows the code for the ProAzureReader class and the ProAzureReaderDataContext class.

Example 6.4. ProAzureReader schema classes

ProAzureReader.cs
public class ProAzureReader : TableStorageEntity
    {
        public ProAzureReader()
        {
            CreateKeys();
        }
        public DateTime PurchaseDate
        { get; set; }
        public DateTime EntryDate
        { get; set; }
        public string Country
        { get;set;}
        public string State
        { get; set; }
        public string City
        { get; set; }
        public string Zip
        { get; set; }
        public string PurchaseLocation
        { get; set; }
        public string PurchaseType
        { get; set; }
        public string ReaderName
        { get; set; }
        public string ReaderUrl
        { get; set; }
        public string Feedback
        { get; set; }

        private void CreateKeys()
        {
         EntryDate = DateTime.UtcNow;
//By Entry Date: [Query: Get records entered today]
            PartitionKey = EntryDate.ToString("MMddyyyy");

            RowKey = string.Format("{0:10}_{1}",
DateTime.MaxValue.Ticks - EntryDate.Ticks, Guid.NewGuid());

        }
    }
ProAzureReaderDataContext.cs
public class ProAzureReaderDataContext : TableStorageDataServiceContext
    {
       public ProAzureReaderDataContext(StorageAccountInfo info)
            : base(info)
        { }

       public IQueryable<ProAzureReader> ProAzureReader
        {
            get
            {
                return this.CreateQuery<ProAzureReader>("ProAzureReader");
            }
        }

       public void AddRecord(
           DateTime purchaseDate,
           string country,
           string state,
           string city,
           string zip,
           string purchaseLocation,
           string purchaseType,
           string readerName,
           string readerUrl,
           string feedback)
       {
           ProAzureReader pa = new ProAzureReader(city);
           pa.Country = country;
           pa.Feedback = feedback;
           pa.PurchaseDate = purchaseDate;
           pa.PurchaseLocation = purchaseLocation;
           pa.PurchaseType = purchaseType;
           pa.ReaderName = readerName;
           pa.ReaderUrl = readerUrl;
           pa.State = state;
           pa.Zip = zip;

           this.AddObject("ProAzureReader", pa);
           this.SaveChanges();
       }
    }

As shown in Listing 6-4, the CreateKeys() methods in the ProAzureReader class sets the values of the PartitionKey and the RowKey. The CreateKeys method is called by the constructor of the class. The PartitionKey and RowKey are a function of the EntryDate property. The RowKey has a GUID associated with it to take into account multiple entities with the same EntryDate. If the dominant query of the application was "Get all the records from a City," you could design the PartitionKey as a function of the City property.

The ProAzureReaderDataContext class defines an AddRecord method for adding a new entity to the table. The method uses the AddObject() and SaveChanges() methods of the base class to save the entity to the table.

Next, you see how to use this schema model in executing table and entity operations.

Note

The code for these schema objects is in the ProAzureTableStorageClasses project in WindowsAzureStorageSolution.sln.

Account Operations

The storage account provides an entry point to the Table service via the Table service endpoint URI. There are no methods at the Account level in the Table service hierarchy. The URI endpoint of a specific account is of the format http://<accountname>.table.core.windows.net.

Table Operations

The Table service defines three methods at the table level of the hierarchy: Create Table, Delete Table, and Query Tables. Table 6-4 lists and describes the three operations, and Table 6-5 lists some important characteristics of these methods.

Table 6.4. Table Operations

Operation

Description

Create Table

Creates a new table under the given storage account. The table is actually created as an entity in a master Tables table.

Delete Table

Marks the specified table and its contents for deletion. The garbage collector deletes marked tables on a periodic basis. So, if you delete a table and try to create it immediately, the Table service complains that the table already exists.

Query Tables

Gets a list of tables from the specified storage account.

Table 6.5. Table Operations Characterstics

Operation

HTTP Verb

Cloud URI

Development Storage URI

HTTP Version

Permissions

Create Table

POST

http://<accountname>.table.core.windows.net/Tables

http://127.0.0.1:10002/<devstorageaccount>/Tables

HTTP/1.1

Only the account owner can call this operation.

Delete Table

DELETE

http://<accountname>.table.core.windows.net/Tables('<tablename>')

http://127.0.0.1:10002/<devstorageaccount>/Tables('<tablename>')

HTTP/1.1

Only the account owner can call this operation.

Query Tables

GET

http://<accountname>.table.core.windows.net/Tables()

http://127.0.0.1:10002/<devstorageaccount>/Tables

HTTP/1.1

Only the account owner can call this operation.

The <account name> is the storage account name in the cloud, and the <devstorageaccount> is the development storage account. Observe that unlike with blob containers, the operations can be called only with account owner privileges. The following sections discuss some of the operations from Table 6-5 in detail. Even though the operations are different, the programming concepts behind them are similar. To keep the book at a conceptual level, I discuss the Create Table and Query Tables operations, because they cover most of the discussed concepts. Studying these operations in detail will enable you to understand the programming concepts behind all the table operations. The ProAzureReaderTracker_WebRole web role included with this chapter's source code contains the implementation of the table operations.

Create Table

The Create Table operation creates a table in the storage account. Behind the scenes, the Table service creates an entity with the specified name in the master table Tables.

The URI for the Create Table operation is of the format http://<accountname>.table.core.windows.net/Tables. Tables give you a structured storage data structure in the cloud. Because of the standard REST interface and Internet scale, you can create tables anywhere, anytime, and in any programming language that supports Internet programming. The Create Table REST request looks like Listing 6-5.

Example 6.5. Create Table REST Request

POST /Tables HTTP/1.1
User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Sun, 21 Jun 2009 18:42:29 GMT
Authorization: SharedKeyLite proazurestorage:
pwFouPw+BPWzlaQPyccII+K8zb+v6qygxZhp9fCdqRA=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Content-Type: application/atom+xml
Host: proazurestorage.table.core.windows.net
Content-Length: 499
Expect: 100-continue
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <title />
  <updated>2009-06-21T18:42:29.656Z</updated>
  <author>
    <name />
  </author>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:TableName>MyFirstAzureTable</d:TableName>
    </m:properties>
  </content>
</entry>

Listing 6-5 shows the request to create a table named MyFirstAzureTable. The POST HTTP verb instructs the Table service to create a table. The request body consists of an ADO.NET entity set in Atom feed format. For the Create Table operation, the Table service responds with a status code of HTTP/1.1 201 Created or HTTP/1.1 409 Conflict if a table with the same name already exists. The Create Table response is shown in Listing 6-6.

Example 6.6. Create Table REST Response

HTTP/1.1 201 Created

Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
Location: http://proazurestorage.table.core.windows.net/Tables('MyFirstAzureTable')
Server: Table service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 7347b966-9efb-4958-bcf5-d3616563fb28
Date: Sun, 21 Jun 2009 18:44:29 GMT
Content-Length: 836

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base=http://proazurestorage.table.core.windows.net/
xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <id>http://proazurestorage.table.core.windows.net/Tables('MyFirstAzureTable')
</id>
  <title type="text"></title>
  <updated>2009-06-21T18:44:29Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="Tables" href="Tables('MyFirstAzureTable')" />
  <category term="proazurestorage.Tables" scheme="http://schemas.micro
soft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:TableName>MyFirstAzureTable</d:TableName>
    </m:properties>
  </content>
</entry>

In Listing 6-6, the first line represents the status code of the operation. The x-ms-request-id represents a unique request identifier that can be used for debugging or tracing. The response body also contains an ADO.NET entity set in Atom feed format.

Figure 6-9 illustrates the Create Table operation in the ProAzureReaderTracker_WebRole web role.

Create Table operation from the ProAzureReaderTracker_WebRole web role

Figure 6.9. Create Table operation from the ProAzureReaderTracker_WebRole web role

As shown in Figure 6-9, to create a table, you must do the following:

  1. Run the ProAzureReaderTracker_WebRole web role locally or in the cloud.

  2. Make sure the appropriate table storage endpoint is specified in the ServiceConfiguration.cscfg file of the ProAzureReaderTracker cloud service.

  3. Make TableOperations.aspx the default start page.

  4. Run ProAzureReaderTracker cloud service.

  5. Enter a table name (such as MyFirstAzureTable) in the text field next to the Create Table button.

  6. Click the Create Table button to create the table in the Table service. If the table is created successfully, it appears in the List of Tables list box.

There is one more way to create a table using the schema model you created earlier in this section. To create a table using the schema model, go to TableOperations.aspx and click the Create ProAzureReader Table link button to create the ProAzureReader table from the schema.

To help you understand the programming model of the Create Table operation, open the Visual Studio Solution Chapter4.sln from the Chapter 4 source directory. The WindowsAzureStorageHelper class in the ProAzureCommonLib contains a helper function called CreateTable(), as shown in Listing 6-7.

Example 6.7. CreateTable() Method in the WindowsAzureStorageHelper Class

public void CreateTable(string tableName)
{
   TableStorageType.CreateTable(tableName);

}

The CreateTable() method calls the CreateTable() method on the TableStorageType object of class TableStorage from the StorageClient library. The project ProAzureTableStorageClasses contains the schema objects for the ProAzureReader table. The project also contains the data source helper class ProAzureReaderDataSource to work with the ProAzureReader table. The ProAzureReaderDataSource class contains a static method CreateTable() specifically to create the ProAzureReader table, as shown in Listing 6-8.

Example 6.8. Create ProAzureReader Table

public static void CreateTable(StorageAccountInfo accountInfo)
 {
    TableStorage.CreateTablesFromModel
(typeof(ProAzureReaderDataContext), accountInfo);
 }

The CreateTable() method in the ProAzureReaderDataSource class calls the static method CreateTablesFromModel() of the TableStorage class from the StorageClient library. The method accepts the type of DataServiceContext class as the parameter. In the schema model from the Pro Azure Reader Tracker example, the ProAzureReaderDataContext class represents an extension of the DataServiceContext class and can be passed to the CreateTablesFromModel() method. Figure 6-10 illustrates the sequence diagram of the Create Table operation for the ProAzureReader table.

Create Table sequence diagram

Figure 6.10. Create Table sequence diagram

As shown in Figure 6-10, the TableOperations.aspx page calls the static method CreateTable() on the ProAzureReaderDataSource class in the ProAzureTableStorageClasses.dll. The ProAzureReaderDataSource object calls the static method CreateTablesFromModel() on TableStorage. The TableStorage class internally calls the CreateTable() method. The CreateTable() method then calls the AddObject() and SaveObject() methods of the DataServiceContext object. The CreateTablesFromModel() method internally calls the AddObject() and SaveChanges() methods of the System.Data.Services.Client.DataServiceContext class. The AddObject() method adds the object to the local collection of objects DataServiceContext is tracking. The SaveChanges() methods saves the collection of tracked objects to storage.

Query Tables

The Query Tables operation returns a list of all the tables in a storage account. The Table service returns a maximum of 1,000 items in a single query. But similar to the NextMarker element you saw in the Blob and Queue services, the Table service returns a pointer x-ms-continuation-NextTableName. You can send a follow-up request to the Table service to retrieve the remaining items by passing x-ms-continuation-NextTableName as a URI parameter. The Query Tables REST request looks like Listing 6-9.

Example 6.9. Query Tables REST Request

GET /Tables()?$top=50 HTTP/1.1
User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Sun, 21 Jun 2009 18:42:10 GMT
Authorization: SharedKeyLite proazurestorage:
hZTV+6FS1lWguxB4vBiDvbubPMALt2kK+kIpVmrYme8=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Host: proazurestorage.table.core.windows.net
Connection: Keep-Alive

In Listing 6-9, the HTTP verb used is GET. Note the URI parameter $top=50; this parameter instructs the Table service to return a maximum of 50 items for this call. Listing 6-10 shows the response from the Table service for the Query Tables operation.

Example 6.10. Query Tables REST Response

HTTP/1.1 200 OK

Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d3ca497d-65d2-4fb6-a51e-3babec57e525
Date: Sun, 21 Jun 2009 18:44:09 GMT
Content-Length: 1630

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base=http://proazurestorage.table.core.windows.net/
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Tables</title>
  <id>http://proazurestorage.table.core.windows.net/Tables</id>
  <updated>2009-06-21T18:44:10Z</updated>
  <link rel="self" title="Tables" href="Tables" />
  <entry>
<id>
        http://proazurestorage.table.core.windows.net/Tables('ProAzureReader')
    </id>
    <title type="text"></title>
    <updated>2009-06-21T18:44:10Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="Tables" href="Tables('ProAzureReader')" />
    <category term="proazurestorage.Tables"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:TableName>ProAzureReader</d:TableName>
      </m:properties>
    </content>
  </entry>
  <entry>
    <id>
        http://proazurestorage.table.core.windows.net/Tables('TestTable1')
    </id>
    <title type="text"></title>
    <updated>2009-06-21T18:44:10Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="Tables" href="Tables('TestTable1')" />
    <category term="proazurestorage.Tables"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:TableName>TestTable1</d:TableName>
      </m:properties>
    </content>
  </entry>
</feed>

As shown in Listing 6-10, the Query Tables response contains two tables ProAzureReader and TestTable1. Figure 6-11 illustrates the Query Tables operation in the ProAzureReaderTracker_WebRole web role.

Query Tables operation in the ProAzureReaderTracker_WebRole web role

Figure 6.11. Query Tables operation in the ProAzureReaderTracker_WebRole web role

As illustrated in Figure 6-11, the TableOperations.aspx loads all the tables from the storage account in the list box.

To help you understand the programming model of the Query Tables operation, open the Visual Studio Solution Chapter4.sln from the Chapter 4 source directory. The WindowsAzureStorageHelper class in ProAzureCommonLib contains a helper method called ListTables(), as shown in Listing 6-11.

Example 6.11. ListTables() Method in the WindowsAzureStorageHelper Class

public IEnumerable<string> ListTables()
{
    return TableStorageType.ListTables();

}

In Listing 6-11, the ListTables() method calls the ListTables() method on the TableStorageType object from StorageClient library. The TableStorage object utilizes the System.Data.Services.Client.DataServiceQuery object to retrieve a list of tables from the Table service.

Figure 6-12 illustrates the sequence diagram for the Query Tables operation.

List Tables sequence diagram

Figure 6.12. List Tables sequence diagram

As illustrated in Figure 6-12, the WindowsAzureStorageHelper object calls the ListTables() method on the TableStorage object from the StorageClient library. The TableStorage object utilizes the DataServiceContext object to create an instance of the DataServiceQuery class. TableStorage then calls the Execute() method on the DataServiceQuery object to retrieve the list of tables from the Table service. The Table service returns a list of all the table names from the storage account.

Entity Operations

Entities support several operations, as listed in Table 6-6.

Table 6.6. Entity Operations

Operation

Description

Query Entities

Queries for a list of entities in a table.

Insert Entity

Adds a new entity to the table.

Update Entity

Updates or replaces an entire entity in the table.

Merge Entity

Only updates the properties of an entity in the table. Properties with null values are ignored by this operation.

Delete Entity

Deletes an existing entity from a table

Table 6-7 lists some of the important characteristics of the entity operations listed in Table 6-6.

Table 6.7. Entity Operations Characterstics

Operation

HTTP Verb

Cloud URI

Development Storage URI

HTTP Version

Permissions

Query Entities

GET

http://<accountname>.table.core.windows.net/<tablename>()?$filter=<query-expression>

http://127.0.0.1:10002/<devstorageaccount>/<tablename>()?$filter=<query-expression>

HTTP/1.1

Only the account owner can call this operation.

Insert Entity

POST

http://<accountname>.table.core.windows.net/<tablename>

http://127.0.0.1:10002/<devstorageaccount>/<tablename>

HTTP/1.1

Only the account owner can call this operation.

Update Entity

PUT

http://<accountname>.table.core.windows.net/<tablename>(PartitionKey="x", RowKey="y")

http://127.0.0.1:10002/<devstorageaccount>/<tablename>(PartitionKey="x", RowKey="y")

HTTP/1.1

Only the account owner can call this operation.

Merge Entity

MERGE

http://<accountname>.table.core.windows.net/<tablename>(PartitionKey="x", RowKey="y")

http://127.0.0.1:10002/<devstorageaccount>/<tablename>(PartitionKey="x", RowKey="y")

HTTP/1.1

Only the account owner can call this operation.

Delete Entity

DELETE

http://<accountname>.table.core.windows.net/<tablename>(PartitionKey="x", RowKey="y")

http://127.0.0.1:10002/<devstorageaccount>/<tablename>(PartitionKey="x", RowKey="y")

HTTP/1.1

Only the account owner can call this operation.

The <account name> is the storage account name in the cloud, and the <devstorageaccount> is the development storage account. The <table name> is the name of the table you want to query on. The following sections discuss some of the operations from Table 6-7 in detail. Even though the operations are different, the programming concepts behind them are similar. To keep the book at a conceptual level, I discuss the Query Entities, Insert Entity, and Merge Entity operations, because they cover most of the discussed concepts. By studying these three operations in detail, you can understand the programming concepts behind all the entity operations.

Query Entities

The URI for the Query Entities operation is of the form http://<accountname>.table.core.windows.net/<tablename>()?$filter=<query-expression> or http://<accountname>.table.core.windows.net/<tablename>(PartitionKey="x", RowKey="y"). Entities are analogous to rows in a relational table. So, you need a flexible mechanism to specify query parameters and filter criteria for the query. The URL parameters for the Query Entities operation support the ADO.NET Data Services query options as defined in the ADO.NET Data Service Specifications.[11] The $filter and $top URL parameters discussed earlier in this section are the most commonly used criteria for querying entities in a table.

You can use LINQ to query entities in a table. When you enumerate over a LINQ statement, the query is created and sent to the server, and results are retrieved. Listing 6-12 shows an example Query Entities REST request.

Example 6.12. Query Entities REST Request

GET /ProAzureReader()?$top=2 HTTP/1.1
User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Mon, 22 Jun 2009 02:35:26 GMT
Authorization: SharedKeyLite
    proazurestorage:K+P5VD/AIhS22b6yui04LR1kxx1V4v4/Cy5rc+5nIr0=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Host: proazurestorage.table.core.windows.net

Listing 6-12 shows the request for querying the ProAzureReader table with a $top=2 criteria to retrieve top two items. The Query Entities operation can return only 1,000 items in a single call. If the number of items that fit the filter criteria is greater than 1,000 or the query times out, the Table services sends two continuation tokens:- x-ms-continuation-NextPartitionKey and x-ms-continuation-NextRowKey in the response. Similar to the NextMarker token you saw in the Blob and Queue services, these tokens point to the first item in the next data set. Listing 6-13 shows the REST response from the Table service for the Query Entities operation.

Example 6.13. Query Entities REST Response

HTTP/1.1 200 OK

Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: ab64434d-9a8d-4090-8397-d8a9dad5da8a
x-ms-continuation-NextPartitionKey: 1!12!MDYyMDIwMDk-
x-ms-continuation-NextRowKey: 1!76!MTI1MjE1NjgwMjgzNzA1MTk5OTlfM
Date: Mon, 22 Jun 2009 02:38:19 GMT
Content-Length: 3592

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base=http://proazurestorage.table.core.windows.net/
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
  <title type="text">ProAzureReader</title>
  <id>http://proazurestorage.table.core.windows.net/ProAzureReader</id>
  <updated>2009-06-22T02:38:19Z</updated>
  <link rel="self" title="ProAzureReader" href="ProAzureReader" />
  <entry m:etag="W/&quot;datetime'2009-06-20T23%3A30%3A15.251Z'&quot;">
    <id>http://proazurestorage.table.core.windows.net/
ProAzureReader(PartitionKey='06202009',RowKey='12521567602930729999')
</id>
    <title type="text"></title>
    <updated>2009-06-22T02:38:19Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="ProAzureReader"
href="ProAzureReader(PartitionKey='06202009',RowKey='12521567602930729999')" />
    <category term="proazurestorage.ProAzureReader"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>06202009</d:PartitionKey>
        <d:RowKey>
         12521567602930729999
        </d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2009-06-20T23:30:15.251Z</d:Timestamp>
        <d:City></d:City>
        <d:Country></d:Country>
        <d:EntryDate m:type="Edm.DateTime">2009-06-20T23:28:26.927Z</d:EntryDate>
        <d:Feedback>Good Book :). But don't write again.</d:Feedback>
        <d:PurchaseDate m:type="Edm.DateTime">2009-06-20T00:00:00Z</d:PurchaseDate>
        <d:PurchaseLocation></d:PurchaseLocation>
        <d:PurchaseType>New</d:PurchaseType>
        <d:ReaderName></d:ReaderName>
        <d:ReaderUrl></d:ReaderUrl>
        <d:State></d:State>
        <d:Zip></d:Zip>
</m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2009-06-20T13%3A01%3A10.5846Z'&quot;">
    <id>http://proazurestorage.table.core.windows.net/
ProAzureReader(PartitionKey='06202009',RowKey='12521567980278019999')
</id>
    <title type="text"></title>
    <updated>2009-06-22T02:38:19Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="ProAzureReader"
href="ProAzureReader(PartitionKey='06202009',RowKey='12521567980278019999')" />
    <category term="proazurestorage.ProAzureReader"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>06202009</d:PartitionKey>
        <d:RowKey>12521567980278019999</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2009-06-20T13:01:10.5846Z</d:Timestamp>
        <d:City>mumbai</d:City>
        <d:Country>india</d:Country>
        <d:EntryDate m:type="Edm.DateTime">2009-06-20T12:59:32.198Z</d:EntryDate>
        <d:Feedback>Good Book :). But don't write again.</d:Feedback>
        <d:PurchaseDate m:type="Edm.DateTime">2009-06-20T00:00:00Z</d:PurchaseDate>
        <d:PurchaseLocation>web</d:PurchaseLocation>
        <d:PurchaseType>New</d:PurchaseType>
        <d:ReaderName>tredkar</d:ReaderName>
        <d:ReaderUrl></d:ReaderUrl>
        <d:State>maharashtra</d:State>
        <d:Zip>400028</d:Zip>
      </m:properties>
    </content>
  </entry>
</feed>

In Listing 6-13, the first line represents the status code of the operation. The response body consists of ADO.NET entity set in Atom feed format. The body shows two items retrieved. The x-ms-continuation-NextPartitionKey and x-ms-continuation-NextRowKey indicate pointers to the first item from the next data set. To retrieve the remaining items, you can pass the two tokens as the NextPartitionKey and NextRowKey URL parameters of a subsequent call. The x-ms-continuation-NextPartitionKey and x-ms-continuation-NextRowKey are used to page on Query Entity results.

In the Pro Azure Reader Tracker application, you can implement the following queries:

  • Get all the entries entered today (the dominant query).

  • Get entries by city, state, or country.

  • Get the Top(n) entries.

  • Get entries by purchase date.

Listing 6-14 shows the implementation of each of these queries using LINQ. The methods are implemented in the ProAzureReaderDataSource class.

Example 6.14. Query Entities in the ProazureReaderDataSource Class

// Get all the entries entered today (The dominant query)
public IEnumerable<ProAzureReader> Select()
 {
var results = from g in dContext.ProAzureReader
where g.PartitionKey == DateTime.UtcNow.ToString("MMddyyyy")
select g;
var r = results.ToArray<ProAzureReader>();
return r;
 }
//Get entries by City
public IEnumerable<ProAzureReader> SelectByCity(string city)
{
var results = from g in dContext.ProAzureReader
where g.PartitionKey == DateTime.UtcNow.ToString("MMddyyyy")
&& g.City == city
select g;
var r = results.ToArray<ProAzureReader>();
return r;
}
//Get entries by State
public IEnumerable<ProAzureReader> SelectByState(string state)
{
var results = from g in dContext.ProAzureReader
where g.PartitionKey == DateTime.UtcNow.ToString("MMddyyyy")
&& g.State == state
select g;
var r = results.ToArray<ProAzureReader>();
return r;
}
//Get entries by Country
public IEnumerable<ProAzureReader> SelectByCountry(string country)
{
var results = from g in dContext.ProAzureReader
where g.PartitionKey == DateTime.UtcNow.ToString("MMddyyyy")
&& g.Country == country
select g;
var r = results.ToArray<ProAzureReader>();
return r;
}

//Get entries by Purchase Date
public IEnumerable<ProAzureReader> SelectByPurchaseDate(DateTime purchaseDate)
{
var results = from g in dContext.ProAzureReader
where g.PurchaseDate.Equals(purchaseDate )
select g;
var r = results.ToArray<ProAzureReader>();
return r;
}
//Get Top(n) entries
public IEnumerable<ProAzureReader> SelectTopN(int topNumber)
{
var results = dContext.ProAzureReader.Take(topNumber);
var r = results.ToArray<ProAzureReader>();
return r;
}

In Listing 6-14, each method implements a LINQ query for Query Entities on the ProAzureReader table. On the Default.aspx page of the ProAzureReaderTracker_WebRole web role is a link button for each of the queries; see Figure 6-13.

Query entities in the ProAzureReaderTracker_WebRole web role

Figure 6.13. Query entities in the ProAzureReaderTracker_WebRole web role

As shown in Figure 6-13, you can specify filter criteria in the filter text box and click one of the link buttons to execute the query.

Tip

If you're running the web application on the local machine, you can run the Fiddler trace tool and capture the request and response contents of each query.

Insert Entity

The Insert Entity operation inserts an entity into the specified table. This operation requires the PartitionKey and RowKey to be specified. The URI for the insert Entity operation is of the format http://<accountname>.table.core.windows.net/<tablename>. A typical Insert Entity REST request looks like Listing 6-15.

Example 6.15. Insert Entity REST Request

POST /ProAzureReader HTTP/1.1

User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Mon, 22 Jun 2009 03:25:47 GMT
Authorization: SharedKeyLite proazurestorage:
    mazZ5pykdE1CmH5+SDe7fqWDLQpnWDcK1pgWDvyzxss=
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Content-Type: application/atom+xml
Host: proazurestorage.table.core.windows.net
Content-Length: 1178
Expect: 100-continue

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <title />
  <updated>2009-06-22T03:25:47.469Z</updated>
  <author>
    <name />
  </author>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:City>san ramon</d:City>
      <d:Country>usa</d:Country>
<d:EntryDate m:type="Edm.DateTime">2009-06-22T03:25:46.976Z</d:EntryDate>
      <d:Feedback>Excellent Book</d:Feedback>
      <d:PartitionKey>06222009</d:PartitionKey>
      <d:PurchaseDate m:type="Edm.DateTime">2009-06-21T00:00:00</d:PurchaseDate>
      <d:PurchaseLocation>amazon.com</d:PurchaseLocation>
      <d:PurchaseType>New</d:PurchaseType>
      <d:ReaderName>tejaswi</d:ReaderName>
      <d:ReaderUrl m:null="false" />
      <d:RowKey>12521566596530239999_7e9f46ea</d:RowKey>
      <d:State>ca</d:State>
      <d:Timestamp m:type="Edm.DateTime">0001-01-01T00:00:00</d:Timestamp>
      <d:Zip>94582</d:Zip>
    </m:properties>
  </content>

In Listing 6-15, the HTTP verb used is POST, to instruct the Table service that this is an insert operation. The request body contains an ADO.NET entity set. The m:properties element defines the property names and values of the entity. In reality, it represents a serialized ProAzureReader object discussed earlier in this chapter. The m:type attribute specifies the data type of the property. The default property is Edm.String if the property is omitted. After the entity is created successfully, the response header consists of an HTTP 1.1 201 (Created) status code. The response body also contains the same ADO.NET entity set that was part of the request body.

The Submit button on the Default.aspx page in the ProAzureReaderTracker_WebRole project is tied to an Insert Entity operation because it inserts a new entity into the ProAzureReader table. Figure 6-14 shows the Default.aspx page.

Default.aspx in the ProAzureReaderTracker_WebRole web role

Figure 6.14. Default.aspx in the ProAzureReaderTracker_WebRole web role

In Figure 6-14, when you enter all the reader properties and click Submit, a new entity is created and the data list is refreshed, showing the new entity at the top. Listing 6-16 shows the code for the btnSubmit_Click event from the Default.aspx.cs file. Listing 6-16 also shows the AddProAzureReader() method from the ProAzureReaderDataSource class, which is called by the btnSubmit_Click method to insert a new entity.

Example 6.16. Insert Entity

//Default.aspx
protected void btnSubmit_Click(object sender, EventArgs e)
{
   try
   {
    ProAzureReader newReader = new ProAzureReader()
    {
     City = txtCity.Text,
     Country = txtCountry.Text,
     Feedback = txtFeedback.Text,
     PurchaseDate = DateTime.Parse(txtPurchaseDate.Text),
     PurchaseType = ddlPurchaseType.SelectedItem.Text,
     PurchaseLocation = txtPurchaseLocation.Text,
     ReaderName = txtName.Text,
     ReaderUrl = txtUrl.Text,
     State = txtState.Text,
     Zip = txtZip.Text


    };
    ProAzureReaderDataSource ds = new ProAzureReaderDataSource();
    ds.AddProAzureReader(newReader);
   }
   catch (Exception ex)
   {

    lblStatus.Text = "Error adding entry " + ex.Message;
   }
  }

//ProAzureDataSource.cs
public void AddProAzureReader(ProAzureReader newItem)
 {
         dContext.AddObject(ENTITY_SET_NAME, newItem);
         dContext.SaveChangesWithRetries(SaveChangesOptions.None);
}

As shown in Listing 6-16, the AddProAzureReader() method calls the AddObject() and SaveChangesWithRetries() methods on the ProAzureReaderDataContext object. SaveChangesOptions is an ADO.NET Data Services enumeration with four possible values:

  • None specifies that the operation is non-batch and should stop if any error occurs.

  • Batch specifies that multiple changes are packaged into one change set and sent to the server in a single call.

  • ContinueOnError specifies that subsequent operations are attempted even if an error occurs in one of the operations.

  • ReplaceOnUpdate replaces all the properties of an entity on the server with the new ones specified.

Figure 6-15 illustrates the sequence diagram for the Insert Entity operation.

Insert Entity sequence diagram

Figure 6.15. Insert Entity sequence diagram

As shown in Figure 6-15, the Default.aspx page calls the AddProAzureReader() method on the ProAzureReaderDataSource object. Default.aspx gathers the user input, creates a new ProAzureReader object, and passes it as a parameter to the AddProAzureReader() method. The AddProAzureReader() method calls the AddObject() and SaveChangesWithRetries() methods on the ProAzureReaderDataSourceContext object, which in turn calls the SaveChanges() method on its parent class (DataServiceContext) object. The SaveChanges() method sends the HTTP request and receives the response from the Table service.

Note

The Table service supports ACID transactions for batch operations on multiple entities on a single partition (with the same PartitionKey).[12] The constraints are as follows: same PartitionKey, one transaction per entity in the batch, no more than 100 transactions in the batch, and the total batch payload size should be less than 4MB.

Merge Entity

The Merge Entity operation updates the properties of an entity without replacing an entity from the specified table. It requires the PartitionKey and RowKey to be specified. The URI for the Merge Entity operation is of the format http://<accountname>.table.core.windows.net/<tablename>(PartitionKey="x", RowKey="y"). A typical Merge Entity REST request looks like Listing 6-17.

Example 6.17. Merge Entity REST Request

MERGE /ProAzureReader(PartitionKey='06222009',RowKey='12521566596530999') HTTP/1.1

User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Mon, 22 Jun 2009 07:02:04 GMT
Authorization: SharedKeyLite proazurestorage:motXsCh9vzZZpNLbJ8xsNgmO95
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 1.0;NetFx
Content-Type: application/atom+xml
If-Match: W/"datetime'2009-06-22T07%3A01%3A13.043Z'"
Host: proazurestorage.table.core.windows.net
Content-Length: 1355
Expect: 100-continue

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices
xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
xmlns="http://www.w3.org/2005/Atom">
  <title />
  <updated>2009-06-22T07:02:04.948Z</updated>
<author>
    <name />
  </author>
  <id>http://proazurestorage.table.core.windows.net/ProAzureReader
(PartitionKey='06222009',RowKey='12521566596530239999')
</id>
  <content type="application/xml">
    <m:properties>
      <d:City>san ramon</d:City>
      <d:Country>usa</d:Country>
      <d:EntryDate m:type="Edm.DateTime">2009-06-22T03:25:46.976Z</d:EntryDate>
      <d:Feedback>Excellent Book</d:Feedback>
      <d:PartitionKey>06222009</d:PartitionKey>
      <d:PurchaseDate m:type="Edm.DateTime">2009-06-21T00:00:00Z</d:PurchaseDate>
      <d:PurchaseLocation>amazon.com</d:PurchaseLocation>
      <d:PurchaseType>New</d:PurchaseType>
      <d:ReaderName>tejaswi</d:ReaderName>
      <d:ReaderUrl>http://www.bing.com</d:ReaderUrl>
      <d:RowKey>12521566596530239999_7e9f46ea-4230-4abb-bbd1</d:RowKey>
      <d:State>ca</d:State>
      <d:Timestamp m:type="Edm.DateTime">2009-06-22T07:01:13.043Z</d:Timestamp>
      <d:Zip>94582</d:Zip>
    </m:properties>
  </content>
</entry>

In Listing 6-17, the HTTP verb specified is MERGE, to instruct the Table service that this is a Merge operation. The request body contains an ADO.NET entity set. The m:properties element define the property names and values of the entity. In the example, it represents a serialized ProAzureReader object as discussed earlier in this chapter. The If-Match header is a required condition the server checks before performing a conditional update. The ETag value of the entity is checked before making the update. For an unconditional update, its value should be a wildcard (*). A successful entity update returns an HTTP 1.1 204 (No Content) status code.

The data list in the Default.aspx page in the ProAzureReaderTracker_WebRole project has an UpdateUrl button that update the ReaderUrl property of an entity. Figure 6-16 shows the Default.aspx page.

Default.aspx in the ProAzureReaderTracker_WebRole web role for the Merge Entity operation

Figure 6.16. Default.aspx in the ProAzureReaderTracker_WebRole web role for the Merge Entity operation

In Figure 6-16, you can update the ReaderUrl property of the ProAzureReader entity. The code for Merge Entity is in the UpdateUrl() method in the ProAzureReaderDataSource class, as shown in Listing 6-18.

Example 6.18. UpdateUrl() Method

public void UpdateUrl(string PartitionKey, string RowKey, string url)
       {
         var results = from g in dContext.ProAzureReader
                       where g.PartitionKey == PartitionKey
                       && g.RowKey == RowKey
                       select g;
         var e = results.FirstOrDefault<ProAzureReader>();
         e.ReaderUrl = url;
         dContext.MergeOption = MergeOption.PreserveChanges;
         dContext.UpdateObject(e);
         dContext.SaveChanges();
        }

As shown in Listing 6-18, the UpdateUrl() method retrieves the appropriate entity using a LINQ query. Then, it sets the merge option to PreserveChanges and calls the UpdateObject() and SaveChanges() methods of the DataServiceContext object. The merge option instructs the DataServiceContext object to track entities in a specific manner locally. The possible options are as follows.

  • AppendOnly instructs the DataServiceContext object to append entities to already-existing entities in the local cache. The existing entities in the cache aren't modified. This is the default option.

  • NoTracking instructs the DataServiceContext object that entities aren't tracked locally. As a result, objects are always loaded from the server. The local values are overwritten with the server values.

  • OverwriteChanges instructs the DataServiceContext object that server values take precedence over client values even if they have changed.

  • PreserveChanges instructs the DataServiceContext object to preserve the local changes even if changes are detected on the server. The DataServiceContext object doesn't overwrite the property values but updates the ETag value with the server ETag value. Any properties not changed locally are updated with latest values from the server. In this option, the local changes to the properties aren't lost.

Figure 6-17 illustrates the sequence diagram for the Merge Entity operation.

Merge Entity sequence diagram

Figure 6.17. Merge Entity sequence diagram

As shown in Figure 6-17, the Default.aspx page calls the UpdateUrl() method on the ProAzureReaderDataSource object. The UpdateUrl() method calls the UpdateObject() and SaveChanges() methods on the ProAzureReaderDataSourceContext object.

Table Service Summary

In this chapter, you learned the details of interacting with the Windows Azure Table Storage service. The Table service provides with structured storage that you can use from anywhere, anytime. If you've already worked with REST, the ADO.NET Data Services Framework, or the ADO.NET Entity Framework, you should find the Table service concepts easy to understand and program with. The knowledge you've acquired from learning Table service REST API, ADO.NET Data Services Client library, Windows Azure SDK StorageClient, and Storage Client code sample library should enable you to build your own Table service applications.

Windows Azure Storage Summary

This concludes a series of three intense chapters covering the wide range of features and functionality offered by the Blob, Queue, and Table services. You learned in detail about the three types of storage services offered by Windows Azure. The demonstrations and API discussions in these chapters should enable you to use the concepts in your own storage applications. You also are better prepared to choose the right combination of storage services for your solution.

The next few chapters cover the Middleware layer of the Windows Azure Platform (a.k.a. .AppFabric as it relates to the Cloud Services Pyramid discussed in Chapter 1.

Bibliography

MSDN. (n.d.). ADO.NET Data Services Specification. Retrieved from MSDN Developer's Network: http://msdn.microsoft.com/en-us/library/cc668808.aspx.

MSDN. (2009, May). Windows Azure Blob — Programming Blob Storage. Retrieved from MSDN: http://go.microsoft.com/fwlink/?LinkId=153400.

MSDN. (2009, May). Windows Azure Queue — Programming Queue Storage. Retrieved from MSDN: http://go.microsoft.com/fwlink/?LinkId=153402.

MSDN. (2009, May). Windows Azure SDK. Retrieved from MSDN: http://msdn.microsoft.com/en-us/library/dd179367.aspx.

MSDN. (2009, May). Windows Azure Table — Programming Table Storage. Retrieved from MSDN: http://go.microsoft.com/fwlink/?LinkId=153401.



[8] Source: Windows Azure SDK documentation

[9] ee http://msdn.microsoft.com/en-us/library/dd135720.aspx/

[10] Source: ADO.NET Data Services Framework, http://msdn.microsoft.com/en-us/library/cc668811.aspx

[11] ADO.NET Data Services Query Options: http://msdn.microsoft.com/en-us/library/cc668809.aspx

[12] Performing Entity Group Transactions: http://msdn.microsoft.com/en-us/library/dd894038.aspx

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.136.119