span.sup { vertical-align:text-top; }

Data Services

Develop Robust and Scalable Apps with SQL Server Data Services

David Robinson

This article discusses:

  • The SSDS data model
  • Managing entities, containers, and authorities
  • Creating a sample Web application
  • Class serialization and deserialization
This article uses the following technologies:
SQL Server

This column is based on a prerelease version of SQL Server Data Services. All information herein is subject to change.

Contents

SSDS Data Model
Building a Classified Ads System
Adding a City
Adding Categories
Updating and Deleting Entities
Adding and Deleting Listing Schemas
The Classifieds Web App
Class Deserialization
Using the Custom Listing Schema.
Looking Forward

The Microsoft data platform is made up of a rich set of products and technologies for all types of data, from unstructured binary data on one end of the spectrum to highly structured online analytical processing (OLAP) cubes on the other. While these technologies address any application scenarios you may have, typically it involves an up-front investment of time and hardware resources before you are able to start coding your solution.

It is often difficult for application developers to estimate the storage and processing requirements that an application will require over its lifecycle. This can be particularly acute when you consider a startup company where funds are tight and the data used to make hardware investment decisions is scarce. To address concerns about these up-front investments, Microsoft has recently added a new technology to its already robust data platform: SQL Server® Data Services (SSDS).

Not a product in the traditional boxed-software terms, SSDS is a robust, scale-free data service that internally uses proven SQL Server technology and exposes its functionality over industry standard Web service interfaces. SSDS provides an easy-to-use, flexible data model that you access via open, industry standard protocols.

Regardless of whether you are developing with the Microsoft® .NET Framework, Java, or other technologies, you can employ SSDS as your data store. In addition, SSDS is designed to allow developers to provision an account quickly and start developing against it immediately.

SSDS provides developers with a data back end that is not constrained by available drive bays or rack space. By using SSDS as your data platform, your application is free to consume as much data as you need. Regardless of whether it's a gigabyte or a petabyte of storage, you will only pay for the resources you consume.

Many Web sites and apps have cyclical access patterns where you need enough capacity for peak loads, even if those loads aren't sustained. By using SSDS as your data back end, you are free to concentrate on your app, not on data platform capacity planning.

In this article I will introduce you to the basics of developing a data solution around SSDS. I will begin by mapping out the data model that is employed by SSDS. Then I will dive into the development details by showing you how I built the simple online classified ads system, shown in Figure 1, using SSDS.

fig01.gif

Figure 1 Sample Classifieds App Hosted by SSDS

SSDS Data Model

SSDS provides a flexible entity-based data model. This model has three main elements—authority, container, and entity—referred to as the ACE concepts (see Figure 2 ). An SSDS authority can be related to a database in the relational world. When you provision an authority, SSDS will create a DNS name for you to access the authority. For instance, if you provision an authority called ssdsdemo, you will access it at the following URI:

ssdsdemo.data.beta.mssds.com

fig02.gif

Figure 2 Basic SSDS Components

An authority is also the unit of geo-location, meaning that the created DNS name will map to a specific Microsoft datacenter where your data is hosted. If you create two authorities—americasdemo.data.beta.mssds.com and europedemo.data.beta.mssds.com—each one will be associated with a different datacenter that is located closest to your users.

An authority contains a collection of containers—a container is similar to a table in a relational database. The key difference is that you attach a schema to a database table to make all rows in the table homogeneous. A container in SSDS does not require a schema, which allows you to store heterogeneous entities in one convenient location. It is simply a collection of entities. In the current release of SSDS, all queries are scoped to a single container.

One other important point to make here is that each container is placed on a different node in the SSDS cluster. In SQL Server, to gain additional performance, you would place different tables on separate spindles to maximize your read/write capabilities. This is similar in SSDS except that instead each container will be on a separate machine. Additional performance gains can be realized by partitioning your data across multiple containers and multithreading your requests since your reads and writes won't be constrained by a single machine.

A final note about containers is that while each container is placed on a separate node in the SSDS cluster, the data is also replicated on several other nodes for disaster recovery purposes. If the machine your container is on fails, one of the backup replicas are promoted automatically to ensure your applications doesn't observe any data or performance loss.

An entity can be compared to a row in a table in a relational database. An entity is simply a property bag of name/value pairs. These name/value pairs are grouped into two categories: distinguished system properties and flexible properties.

Distinguished system properties are common to all entities and include ID, Kind, and Version. ID uniquely identifies the entity. IDs must be unique within the container it exists in, but different containers can have entities with the same ID. Kind is used to categorize similar entities together. There is no schema attached to the entity, so having entities of the same Kind does not guarantee the same structure, and Version is used to identify the current version of the entity. This value gets updated on each operation.

Flexible properties are where the developer stores the application data. Flexible properties support simple types: string, decimal, bool, datetime, and binary. Each flexible property is indexed up to the first 256 bytes.

Building a Classified Ads System

To demonstrate the features of SSDS and to give an overview of the development experience, I will walk you through the implementation of a sample online classifieds system, Contoso Classifieds. The sample comprises two separate applications: a Windows® Forms application to administer the system and an ASP.NET application—the main Contoso Classifieds site—that users would visit. The application accesses SSDS via a SOAP interface in the Windows Forms application and through the Representational State Transfer (REST) interface within the ASP.NET application.

When you sign up for a SSDS account, you will receive a user name and password. From there it is up to you to begin building your authorities, containers, and entities. For Contoso Classifieds, I set up the authority as contosoclassifieds and the service URI endpoint in the beta release of SSDS is data.data.beta.mssds.com.

The contosoclassifieds authority includes two containers: Categories and Cities. Categories will contain the entities related to the Listing categories. Cities will contain an entity for each city defined in the system. Those entities will simply be pointers to city-specific authorities. Each city-specific authority will have a container per listing category (see Figure 3).

fig03.gif

Figure 3 Contoso Classifieds Elements

There are a few reasons I went with this design. I chose to implement on authority per city so that I can ensure the authority is provisioned in a datacenter that is geographically close to the population it will serve. I chose one container per listing category so that I have a simple query pattern and I can spread the load across multiple machines in the cluster (remember a container is scoped to a specific node in the back-end cluster). Figure 4 shows the user interface for the Contoso Classifieds Administration client.

fig04.gif

Figure 4 Classifieds App Administrative Client

As previously mentioned, it is up to the developer to create any authorities, containers, and entities the application will use. In the sample app, all this work is contained within the click event for the Perform Initial Setup button. (The full code, including error handling, is included in the code download for this article.)

Provisioning an SSDS authority is extremely easy. In this example code I use the SitkaSoapServiceClient, for which I had previously added a service reference to the SSDS SOAP interface:

using (SSDSClient.SitkaSoapServiceClient ssdsProxy = 
  new SSDSClient.SitkaSoapServiceClient())

You have already seen an Authority, but what is an SSDS scope? The scope object is used within SSDS to provide a way of addressing objects in the SOAP service similar to the way URIs are used in the REST service.

The first thing you should do is set the user name and password for the service:

ssdsProxy.ClientCredentials.UserName.UserName = 
  txtUserName.Text;
ssdsProxy.ClientCredentials.UserName.Password = 
  txtPassword.Text;

You need to create an authority, which is at the highest level of the ACE model, so you first create an empty scope:

SSDSClient.Scope serviceScope = new SSDSClient.Scope();

Now create the Contoso Classifieds Authority. This will hold general configuration information in the system

SSDSClient.Authority contosoAuth = 
  new SSDSClient.Authority();
contosoAuth.Id = "contosoclassifieds";

and submit the creation to SSDS

ssdsProxy.Create(serviceScope, contosoAuth);

Now that the main Contoso Classifieds Authority has been created, point your scope to it, then create the container to hold the cities you will be serving classified ads to, and submit the container creation to SSDS:

serviceScope.AuthorityId = contosoAuth.Id;

SSDSClient.Container citiesContainer = new SSDSClient.Container();
citiesContainer.Id = "Cities";

ssdsProxy.Create(serviceScope, citiesContainer);

The process to create a container is similar to the process of creating an authority. The only difference is that you create a Container object instead of an Authority object and, instead of an empty scope, you update the scope to point to the authority in which the container is to be created.

Create the container that will hold all the Header and Listing categories you will support within the application:

SSDSClient.Container categoriesContainer = 
  new SSDSClient.Container();
categoriesContainer.Id = "Categories";

ssdsProxy.Create(serviceScope, categoriesContainer);

Once the authority is created you can point a Web browser to it, using either HTTP or HTTPS, and you can view the container's contents using the REST interface. Simply use the new DNS name that SSDS created when setting up the authority. In this case, the URL would be:

https://contosoclassifieds.data.data.beta.mssds.com/v1

After this URL is entered into the browser, you will be prompted to authenticate. Once you successfully authenticate, you will see something like this in your browser:

<s:Authority xmlns:s="https://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="https://www.w3.org/2001/XMLSchema">
  <s:Id>contosoclassifieds</s:Id>
  <s:Version>11</s:Id>
</s:Authority>

The authority name is lower-cased because since SSDS creates a DNS entry for the authority, you are required to follow DNS naming conventions.

After creating the container, if you refresh the browser and add an empty query to your URL in the form of ?q="", you will see a single Container object being returned in the EntitySet. An EntitySet is simply a collection of entities that are returned as the response to a query. Upon completion of initial setup the sample application will have created one authority (contosoclassifieds) and two containers (Categories and Cities):

<s:EntitySet xmlns:s="https://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="https://www.w3.org/2001/XMLSchema">
  <s:Container>
    <s:Id>Categories</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
  <s:Container>
    <s:Id>Cities</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
</s:EntitySet>

Now that the initial setup is complete, let's move ahead and add the cities-served functionality.

Adding a City

As mentioned earlier, Contoso Classifieds places each city's listings in its own authority, which enables you to choose the datacenter it is hosted in. (While that feature is not currently supported in the beta of SSDS, it will be supported by the time the product is released.)

The code for adding the city is similar to the code used for the initial setup. Using the SOAP proxy, set the credentials, create an empty scope, set the authority ID, and issue the create:

SSDSClient.Authority cityAuth = 
  new SSDSClient.Authority();
cityAuth.Id = cityAuthorityName;

ssdsProxy.Create(serviceScope, cityAuth);

Now that the new city authority is created, add a pointer entity to the Cities container in the main contosoclassifieds authority:

serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Cities";

//Create the City Entity, and set its properties appropriately
SSDSClient.Entity cityEntity = new SSDSClient.Entity();
cityEntity.Id = cityAuthorityName;
cityEntity.Kind = "CityServed";
cityEntity.Properties = new Dictionary<string, object>();
cityEntity.Properties["AuthorityUri"] = 
  string.Format("{0}.data.data.beta.mssds.com/v1/", cityAuthorityName);
cityEntity.Properties["Name"] = txtCity.Text;

//Issue the create to SSDS
ssdsProxy.Create(serviceScope, cityEntity);

Let's recap for a moment. I've created a main contosoclassifieds authority that holds all of the systemwide data. In that authority, there is a Cities container that has an entity for each city the application will serve. That entity contains the display name of the city, along with a pointer to the authority that contains all the listings.

Adding Categories

Up to this point, you've just added authorities, containers, and entities. The next step is to implement the listing categories feature. To do so involves using the query, update, and delete functionality of SSDS.

Contoso Classifieds will support a category header, with sub categories below it to hold the actual postings from users. If you were to use a relational model for this data, you would typically use a header/line pattern for the data. The flexible entity data model used by SSDS, however, allows your data to take whatever shape you like. In the case of the Contoso Classifieds application, I chose to use a single entity to represent each category header and all the subcategories that belong to it (see Figure 5).

fig05.gif

Figure 5 Entity Data Model Used by the App

Please note that I am using this pattern to simply show the flexible nature of an entity and to illustrate that, although multiple entities can have the same Kind, that does not force the entities to be homogeneous. The entity can have whatever shape you would like.

A better approach to this scenario might be to have two different entity Kinds, Category and Listing Category, and to have a flex property of CategoryID in all entities. That would allow you to issue a query such as:

from e in entities where e["CategoryID"] ==­    "For Sale" select e

Notice I say flex property. As previously mentioned, entities in a container must have unique IDs that will limit you from using the distinguished system property ID. Also notice the query syntax I have issued. SSDS uses a LINQ-like query syntax, which should look familiar to most developers who use the .NET Framework.

If you look back at the administrative application shown in Figure 4, you will notice a tree view that represents the listing categories and two textboxes for adding category headers and listing categories. Let's dive into the implementation behind adding a category header.

As with the previous code, you use the SitkaSOAPService­Client, set the credentials, and set the scope to the contosoclassifieds authority and the Categories container. Now all you need to do is create the entity:

SSDSClient.Scope serviceScope = new SSDSClient.Scope();

serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";

SSDSClient.Entity categoryHeaderEntity = new SSDSClient.Entity();
categoryHeaderEntity.Id = CategoryID;
categoryHeaderEntity.Kind = "Category";

You can also add a single flex property that will contain the category name. Notice that this only adds a single property to the flex properties collection:

categoryHeaderEntity.Properties = 
  new Dictionary<string, object>();
categoryHeaderEntity.Properties["CategoryName"] = CategoryName;

The next step is to add the listing categories. Since Contoso Classifieds uses a single entity to represent the entire collection of listing categories within a category header, you need to retrieve the category entity, add the additional listing categories (which will be added as additional flex properties), and submit the update to SSDS.

To retrieve the category entity, set the scope to point directly to it and call the Get method, which will retrieve the entity directly without having to issue a query:

SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = CategoryID;
//Retrieve the Category Entity
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);

Now just determine the index of the new property, add it as an additional flex property, and issue the update (see Figure 6).

Figure 6 Adding Flex Properties

//Determine whether this is the first listing
//category being added to this header
if (categoryEntity.Properties.Count > 2) {
  propCount = ((categoryEntity.Properties.Count - 1) / 2);
  propCount++;
}
else {
  propCount = 1;
}

//Create the FlexProperties for the new listing category
string listingIdPropName = string.Format  ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format  ("ListingCategoryName{0}", propCount);

categoryEntity.Properties[listingIdPropName] = ListingCategoryID;
categoryEntity.Properties[listingNamePropName] = ListingCategoryName;

//Issue the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);

Updating and Deleting Entities

Updating listing categories is as simple as adding an additional flex property. For each update, you are required to retrieve the entity, make any updates locally and submit the update, passing the updated entity along with the scope to the Update method.

Begin by setting the service scope to the main contosoclassifieds authority. Also point it to the Categories container and the entity for the category you are updating:

SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = currentHdrNode.Name;

Get the entity you are going to be updating:

SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);

Now you can reinitialize the flex properties collection, re-adding all the flex properties for the listing categories. Alternatively, you could have looped through each flex property and made the updates, but due to the simple structure of this entity, it's easier to just recreate the entity as shown in Figure 7.

Figure 7 Updating Properties

categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = currentHdrNode.Text;

int propCount = 1;

if (e.Node.Parent != null) {
  //Loop through each node and add its category ID and
  //category name flex property.
  foreach (TreeNode node in e.Node.Parent.Nodes) {
    string listingIdPropName = string.Format      ("ListingCategoryID{0}", propCount);
    string listingNamePropName = string.Format      ("ListingCategoryName{0}", propCount);

    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    //if we are re-adding the updated category, the tree view 
    //will still have its old value, so set the flex property
    //to the updated value
    if (node.Name == e.Node.Name) {
      categoryEntity.Properties[listingIdPropName] =         m_OldSelectNode.Name;
      categoryEntity.Properties[listingNamePropName] = e.Label;
    }

    propCount++;
  }
}

//Submit the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);

To delete an entity or a container, point ServiceScope to the item and call the Delete method of SitkaSoapServiceClient. If it is a Header, delete the entire entity:

if (m_OldSelectNode.Parent == null) {
  ssdsProxy.Delete(serviceScope);
}

For other entities, just as you did with the update, recreate the flex property collection and rebuild, performing the add for all nodes except the deleted node:

SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = CategoryName;

int propCount = 1;

foreach (TreeNode node in entityNode.Nodes) {
  string listingIdPropName = string.Format    ("ListingCategoryID{0}", propCount);
  string listingNamePropName = string.Format    ("ListingCategoryName{0}", propCount);

  if (node.Text != m_OldSelectNode.Text) {
    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    propCount++;
  }
}

Finally, you will issue the update to SSDS and remove the node from the tree view:

ssdsProxy.Update(serviceScope, categoryEntity);

tvCategories.Nodes.Remove(m_OldSelectNode);

Adding and Deleting Listing Schemas

The current beta of SSDS does not support schemas. While there are plans to add schema support in the future, adding schemas yourself within SSDS is an easy task. The only caveat is that you need to manage the schema from within your application because, at this time, SSDS will not enforce it.

For Contoso Classifieds, it seemed like a good idea for the administrator to define a custom schema for certain listing categories. You can define a schema inside the administrative client and use it later on when implementing the Web site.

Up to this point there are no heterogeneous entities inside the same container. Since each entity has a Kind property, you can use this to store different entity types within the same container and also to query for entities of a specific type or, in this case, Kind. While there is no schema contract associated with Kind, it is easy to use this mechanism to group similar data. Now I will add a new entity of Kind ListingSchema to the Categories container.

The dialog for adding a listing schema is quite simple, as shown in Figure 8. It allows the administrator to define additional fields that are associated with a listing and to identify certain fields as being required. I will go into more detail about this later when I show you how to implement the actual Web site.

fig08.gif

Figure 8 Schema Customization for a Listing

The process used to add the ListingSchema entity is the same one you've already seen, but this time using a different entity (Kind). The code for the adding the ListingSchema is shown in Figure 9. After adding a listing schema for a For Sale Cars category, notice that the contents of the Categories container include two different entity types, with completely different data in the same container.

Figure 9 Adding a Schema

listingSchemaEntity.Kind = "ListingSchema";
listingSchemaEntity.Properties = new Dictionary<string, object>();
listingSchemaEntity.Properties["ListingID"] = m_ListingID;
listingSchemaEntity.Properties["ListingName"] = m_ListingName;

int propCount = 0;
bool required = false;

foreach (DataGridViewRow row in dataGridView1.Rows) {
  if (row.IsNewRow) continue;
  propCount++;

  listingSchemaEntity.Properties[string.Format    ("Property{0}Name",propCount)] = 
    row.Cells["colFieldName"].Value.ToString();
  listingSchemaEntity.Properties[string.Format    ("Property{0}DataType", propCount)] = 
    row.Cells["colDataType"].Value.ToString();

  if (row.Cells["colRequired"].Value == null) {
    required = false;
  }
  else {
    required = Convert.ToBoolean(row.Cells      ["colRequired"].Value.ToString());
  }

  listingSchemaEntity.Properties[string.Format    ("Property{0}Required", propCount)] = required;
}

if (propCount > 0) {
  using (SSDSClient.SitkaSoapServiceClient ssdsProxy =     new SSDSClient.SitkaSoapServiceClient()) {
    //Set username and password for the service
    ...

    //Set service scope to the main contoso classifieds
    //authority. Point it to the categories container
    ...

    //If this entity doesn't exist, create it
    if (listingSchemaID == "" || listingSchemaID == null) {
      ssdsProxy.Create(serviceScope, listingSchemaEntity);
    }
    //Otherwise update it
    else {
      serviceScope.EntityId = listingSchemaID;
      ssdsProxy.Update(serviceScope, listingSchemaEntity);
    }
  }
  MessageBox.Show("Custom Schema Saved", "Custom Schema Saved",
    MessageBoxButtons.OK, MessageBoxIcon.Information);
  this.Hide();
}

The last piece of the admin client is to delete a custom listing schema when a listing category is deleted. I previously discussed deleting entities and containers, but then I knew the ID of the entity or container I wanted to delete. Deleting a schema means first issuing a LINQ query to retrieve all listing schemas that are attached to the listing categories and deleting them. The query looks like this:

string.Format(@"from e in entities 
  where e.Id == ""{0}"" select e", CategoryID);

That query will return a list of ListingSchema entities, and all you need to do at this point is delete them. The entire DeleteListingSchema method is shown in Figure 10.

Figure 10 DeleteListingSchema

//Retrieve the custom schema entity for this listing
string linqQuery =   string.Format(
  @"from e in entities where e.Id == ""{0}"" select e", CategoryID);

List<SSDSClient.Entity> entities =
  ssdsProxy.Query(serviceScope, linqQuery);

foreach (SSDSClient.Entity entity in entities) {

  //If more than 1 flex property on the entity, the
  //header has listing categories attached to it.
  if (entity.Properties.Count > 1) {
    //Calculate the number of Listing Categories
    int propCount = ((entity.Properties.Count - 1) / 2);
    for (int x = 1; x < propCount + 1; x++) {
      //Delete the listing schema for each listing
      //in this category
      DeleteListingSchema(entity.Properties["ListingCategoryID" + 
        x.ToString()].ToString());
    }
  }
}

At this point the Contoso Classifieds Administration client is complete and I can move on the implementing the Web site using the REST interface of SSDS.

The Classifieds Web App

As mentioned earlier, you can access SSDS via its REST interface as well as its SOAP interface (as I did for the Contoso Classifieds Administration client). The concepts and features are the same regardless of the interface you choose, but with REST you will make direct HTTP (or HTTPS) calls.

To start, look back at the main interface of the Web app shown in Figure 1. There is a tree view that represents the listing categories, a main content area, and a Datalist of all the cities that the application is currently configured to support. Looking into each one, I'll show you how easy it is to develop an ASP.NET app against SSDS.

If you look at the ASPX code for the tree view, you'll see that it is quite simple:

<h3>Groups</h3>
<asp:TreeView ID="TreeView1" runat="server" 
  onselectednodechanged="TreeView1_SelectedNodeChanged">
</asp:TreeView>

The magic happens in the code behind, as you can see in Figure 11. The first thing for you to be concerned with is the building of the URI, which is the pointer to the Categories container. Since this data is not city-specific, I have chosen to store it in the main contosoclassifieds authority, within the Categories container.

Figure 11 Building the Tree View

TreeView1.Nodes.Clear();

appDataUri = string.Format(@"https://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
query = @"from e in entities where e.Kind == ""Category"" select e";

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = 
  HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//Category");

int nodeIndex = 0;

foreach (XmlNode node in nodeList) {
  if (node.ChildNodes.Count > 3) {
    int propCount = ((node.ChildNodes.Count - 1) / 2);

    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText, 
      node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);

    for(int x=1;x<propCount;x++) {
      tn = new TreeNode(node.ChildNodes[(x*2)+2].InnerText, 
        node.ChildNodes[(x*2)+1].InnerText);
      TreeView1.Nodes[nodeIndex].ChildNodes.Add(tn);
    }      
  }
  else {
    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText,       node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);
  }

  nodeIndex++;
}

Following that, I proceed to use a LINQ query to retrieve all entities with a Kind category, and then I use a UriBuilder object to put it all together and add the necessary HTTP escaping. Next, I go ahead and call the GetHTTPWebRequest method, passing in the escaped URI and a NetworkCredential object with the SSDS user name and password. GetHTTPWebRequest returns a string representation of an EntitySet with all Entities that satisfy the query predicates.

GetHTTPWebRequest is a static helper method to hide some of the HTTP details:

WebRequest request = 
  HttpWebRequest.Create(Uri.EscapeUriString(serviceUri));
request.Credentials = requestCredential;
request.Method = "GET";
request.ContentType = XmlContentType;

// Get the response and read it in to a string.
using (HttpWebResponse response = 
  (HttpWebResponse)request.GetResponse()) {

  return ReadResponse(response);
}

Now all that needs to be done is to create a new Web­Request object, set the Credentials that were passed to it, set the appropriate HTTP verb, set the ContentType to XML, and call the Get­Response method. I then call ReadResponse, which is another static helper method which uses a stream reader to read the HTTPResponse and return it to the caller. The code behind then takes the returned XML, loads it up into a XmlDocument, and loads the tree view with it.

Class Deserialization

Up to this point, the code has simply been manually manipulating XML, but it's easy to take an entity and deserialize it into a class. In this case, the application will be using a CityServed class.

The base Entity class is pretty basic. The class contains properties for both the ID and Version, which are common to all entities. It also contains the necessary attributes so that the class is serializable.

CityServed, shown in Figure 12, inherits from the base Entity class. A generic Query method returns a list of entities of type CityServed:

foreach (CityServed i in HTTPHelper.Query<CityServed>(
  appDataUri, query, new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword)))

Figure 12 CityServed

[XmlRoot(ElementName = "CityServed", Namespace = "")]

public class CityServed : Entity {
  [XmlElement(ElementName = "AuthorityUri")]
  public object AuthorityUriField;

  [XmlElement(ElementName = "Name")]
  public object NameField;

  public override string ToString() {
    return Name;
  }

  [XmlIgnore]
  public string AuthorityUri {
    get { return (string)AuthorityUriField; }
    set { AuthorityUriField = value; }
  }

  [XmlIgnore]
  public string Name {
    get { return (string)NameField; }
    set { NameField = value; }
  }
}

The Query method, included in the code download for this article, is simply a generic static helper method that calls the same GetHTTPWebRequest method as we had used earlier. The Serialize and Deserialize methods are shown in Figure 13. The Query method, combined with the Serialize and Deserialize methods, gives you the ability to use strongly typed .NET objects and save them in SSDS.

Figure 13 Serializing and Deserializing Entities

private static T Deserialize<T>(Stream stm, string xmlPayload) {
  XmlSerializer ser = new XmlSerializer(typeof(T));

  T flex = (T)ser.Deserialize(stm);

  XmlDocument xDom = new XmlDocument();
  xDom.LoadXml(xmlPayload);

  return flex;
}

public static T Deserialize<T>(String xmlPayload) {
  using (MemoryStream stm = new MemoryStream()) {
    Encoding encoding = new UTF8Encoding(false);
    stm.Write(encoding.GetBytes(xmlPayload), 0, encoding.GetByteCount(xmlPayload));
    stm.Position = 0;
    return Deserialize<T>(stm, xmlPayload);
  }
}

public static string Serialize<T>(T flex) {
  using (MemoryStream stm = new MemoryStream()) {
    Serialize(stm, flex);

    stm.Position = 0;

    using (StreamReader reader = new StreamReader(stm)) {
      return reader.ReadToEnd();
    }
  }
}
private static void Serialize<T>(Stream stm, T flex) {
  XmlSerializer ser = new XmlSerializer(typeof(T));
  Encoding encoding = new UTF8Encoding(false);
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.CloseOutput = false;
  settings.ConformanceLevel = ConformanceLevel.Document;
  settings.Encoding = encoding;
  settings.Indent = true;
  settings.OmitXmlDeclaration = true;

  using (XmlWriter writer = XmlWriter.Create(stm, settings)) {
    ser.Serialize(writer, flex);
  }
}

Using the Custom Listing Schema.

As previously mentioned, SSDS entities are flexible, meaning that each entity can have whatever shape you require. The Contoso Classifieds Administration client, for example, gave you the ability to add a schema to a listing category. You can also pull the custom schema out of SSDS and dynamically create an entry form that can be used to add listings to the system.

The first step is to create a DataTable with four columns: Field, Value, DataType and Required. This table will hold the fields the administrator has defined for the listing. Next I will query to see whether a customs schema has been defined for the listing:

string appDataUri = string.Format(@"https://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
string query = string.Format(
  @"from e in entities where e[""ListingID""] == ""{0}"" select e", 
  listingCategoryID);

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//ListingSchema");

I can then load the returned XML into the DataTable and bind it to the DataList.

In the REST interface, you need to build the XML representation of the entity and issue a HTTP POST to the container into which you want insert the entity. Building the entity is easy—you can simply use a boilerplate entity and loop through each field, adding it as a node in the XML document:

foreach (DataListItem fieldItem in dlAddFields.Items) {
  Label lblField = (Label)fieldItem.FindControl("lblAddField");
  TextBox tbItem = (TextBox)fieldItem.FindControl("txtAddValue");
  Label lblFieldType = (Label)fieldItem.FindControl("lblAddFieldType");
  entity = entity + String.Format(
    @" <{0} xsi:type='x:{1}'>{2}</{0}>", lblField.Text.Replace(" ", ""), 
    lblFieldType.Text, tbItem.Text);
}

Finally, build the URI that points to the container to which you want to POST and issue the POST:

string serviceUri = string.Format(@"https://{0}.{1}{2}", 
  postingAuthority, conSSDSUri, postingContainer);

HTTPHelper.PostHTTPWebRequest(serviceUri, entity, 
  new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword));

Looking Forward

SSDS is easy to develop against. The one point I would like to reiterate is that SSDS is built upon proven SQL Server and Windows Server® technology. While the team has chosen to only reveal a limited set of features in the initial beta release of SSDS, there is a lot more to come. The subset of functionality available now will assist many of the scenarios that customers need to address.

Additionally, you can expect to see support for SSDS being added to the other products in the SQL Server suite of products and tools. So, whether you're a developer using C#, Visual Basic®, Java, Ruby, or even Microsoft Office Access®, SSDS with its support for open protocols is an ideal place for you to store your application's data. To learn more about it, visit the SSDS site at microsoft.com/sql/dataservices and sign up for the beta. If there are any additional features you would like to see in the product, please contact me at david.robinson@microsoft.com.

David Robinson is a Senior Program Manager on the SQL Server Data Services team at Microsoft. He spends his time driving new and compelling features into the product. He also enjoys doing presentations at community events and getting feedback from customers on SSDS.