The preferred mechanism to code against the Azure Table service on the Microsoft stack is through ADO.NET Data Services.
Note: If you’re familiar with ADO.NET Data Services (you might remember it by its codename, Astoria), you can skip over this section without missing anything. Note that this is quite different from standard ADO.NET, despite the name.
As of this writing, ADO.NET Data Services was being renamed to WCF Data Services. If you search the Web, you might see documentation and posts reflecting the new name. Don’t worry, though, the technology is absolutely the same.
When the Red Dog team was designing what would become the Azure storage services, they knew they needed a way to let people model and query their data through an HTTP API. Instead of designing something from scratch, they found that another technology at Microsoft was designed for this specific purpose.
ADO.NET Data Services shipped along with .NET 3.5 Service Pack 1. It enables people to expose data via web services accessed over HTTP. The data is addressed through a RESTful URI. Customers can use it to both expose data services and consume data services. This discussion will deal only with the latter, since Azure’s Table service takes care of exposing your data.
You can write code in .NET to consume these services, and query against the data using LINQ. Just remember that ADO.NET Data Services lets you expose data over HTTP, and on the client side it lets you write data access code (CRUD) against these services.
Exposing Data Services
In essence, you can pretend that data stored in some storage system (be it SQL Server or a file) is actually a plain Common Language Runtime (CLR) object. It may not have all the semantics of a Plain Old CLR Object (POCO), but most times it works just as well.
Example 10-1. Plain-vanilla CLR class
public class Cylon
{
public int ID { get; set; }
public String Name { get; set; }
}Note: The choice of ID for the property isn’t arbitrary. ADO.NET Data Services needs a “key” for each entity to uniquely identify it. If you have a property named ID or <Typename>ID, it automatically uses that as the key. You can use the [DataServiceKey] attribute if you want to use another property as the key.
Let’s assume that you have a bunch of Employee objects, loaded from a data source somewhere. To enable people to access them as a RESTful service over HTTP, you must follow these steps:
- Implement a web service that wraps around these objects.
- Add code to map these objects to a JSON or XML representation.
- Write code to perform CRUD operations.
- Write code to perform queries. (This becomes difficult very quickly when you have to deal with arbitrary queries.)
- Write client-side code that knows how to parse data from this service, and knows how to call such a service.
Note: This book touches only lightly on the server-side aspects of ADO.NET Data Services.
Let’s turn this into a Cylon web service that lets you query and add Cylons. Note that this work is done for you in Windows Azure. The following examination walks you through this so that you may understand how all the pieces fit together.
Note: A Cylon (Cybernetic Lifeform Node) is a “race” of machines depicted in the television series Battlestar Gallactica.
2. You must then create a “data source” class that wraps around the Cylon class. Usually, ADO.NET Data Services is used to wrap around Entity Framework data, and it generates this automatically. This class needs a property that returns an IQueryable over the objects you want to query. The code in Example 10-2 does just that. It creates a list of Cylons, and exposes them through a property of the same name. If you want to add support for adding/removing/updating entities, you must implement IUpdateable, but that is not necessary in Example 10-2.
Example 10-2. Generating a list of Cylons
public class CylonDataModel
{
private List<Cylon> _emps;
public CylonDataModel()
{
_emps = new List<Cylon> {
new Cylon(){ID = 1, Name="Cavil"},
new Cylon(){ID = 2, Name = "Leoben"},
new Cylon(){ID = 3, Name = "D'Anna"},
new Cylon(){ID = 4, Name = "Simon"},
new Cylon(){ID = 5, Name ="Aaron"},
new Cylon(){ID = 6, Name ="Six"},
new Cylon(){ID = 7, Name = "Daniel"},
new Cylon(){ID = 8, Name = "Sharon"}
};
}
public IQueryable<Cylon> Cylons
{
get { return _emps.AsQueryable<Cylon>(); }
}
}Note: If you can’t see “ADO.NET Data Service” in the New Item dialog box, ensure that you have .NET 3.5 SP1 and Visual Studio 2008 SP1 installed.
Example 10-3. Cylon data service
public class CylonService : DataService<CylonDataModel>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetServiceOperationAccessRule("*",
ServiceOperationRights.All);
}
}You now have a working ADO.NET Data Service. Let’s take it for a spin. Press F5 and Visual Studio will launch the service in a browser. Visual Studio will run this on localhost and a random port. You can now query the service using any HTTP client or web browser. Example 10-4 shows the results.
Example 10-4. Querying the Cylon service
http://localhost:109...vice.svc/Cylons
?xml version="1.0" encoding="utf-8" standalone="yes"?> <feed xml:base=http://localhost:1096/CylonService.svc/ 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">Cylons</title> <id>http://localhost:1096/CylonService.svc/Cylons</id> <updated>2009-04-16T08:06:17Z</updated> <link rel="self" title="Cylons" href="Cylons" /> <entry> <id>http://localhost:1096/CylonService.svc/Cylons(1)</id> <title type="text"></title> <updated>2009-04-16T08:06:17Z</updated> <author> <name /> </author> <link rel="edit" title="Cylon" href="Cylons(1)" /> <category term="WebApplication1.Cylon" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <content type="application/xml"> <m:properties> <d:ID m:type="Edm.Int32">1</d:ID> <d:Name>Cavil</d:Name> </m:properties> </content> </entry> ...
If you look closely at the verbose XML you get back, you can see that it is an XML representation of the Cylon objects. Even better, it is a fully Atom-compliant feed. (You can also get back JSON if you choose to by sending an additional HTTP header.) Essentially, by hitting the /Cylons URL, you hit the Cylons property in your CylonDataModel class. Since you haven’t specified any sort of filtering, you get back all the items in the list.
The real power of ADO.NET Data Services shows up in automatically parsing queries and returning results. Let’s walk through a few examples of that. Example 10-5 shows how you can use the $top parameter to return the first N elements (in this case, just 1).
Example 10-5. Queries through the URI
http://localhost:109...c/Cylons?$top=1
<?xml version="1.0" encoding="utf-8" standalone="yes" ?> <feed xml:base="http://localhost:1096/CylonService.svc/" 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">Cylons</title> <id>http://localhost:1096/CylonService.svc/Cylons</id> <updated>2009-04-16T08:43:14Z</updated> <link rel="self" title="Cylons" href="Cylons" /> <entry> <id>http://localhost:1096/CylonService.svc/Cylons(1)</id> <title type="text" /> <updated>2009-04-16T08:43:14Z</updated> <author> <name /> </author> <link rel="edit" title="Cylon" href="Cylons(1)" /> <category term="WebApplication1.Cylon" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <content type="application/xml"> <m:properties> <d:ID m:type="Edm.Int32">1</d:ID> <d:Name>Cavil</d:Name> </m:properties> </content> </entry> </feed>
You can perform arbitrary filtering using the $filter parameter. Example 10-6 shows how to use the $filter parameter to retrieve only Cylons that match the filter parameters. (You can see how MSDN documents all the clauses you can use for the $filter clause by visiting http://msdn.microsof...cc668778.aspx.)
Example 10-6. Basic filtering
Return entries with ID greater than 6
http://localhost:109...ons?$filter=(ID gt 6)
Return entries where the Name property is 'Sharon'
http://localhost:109...s?$filter=(Name eq 'Sharon')
Note: ADO.NET Data Services supports several complex filtering operations, as well as aggregate functions (such as sum and average) and expansions. These are not examined here, primarily because Azure’s Table service doesn’t support any URI operations other than $filter and $top.
Awesome, isn’t it? You just got a ton of querying support for free. Since the output is very web standards friendly, it is easy to build clients in any language/toolkit. This discussion showed you how ADO.NET Data Services integrates with .NET and provides rich querying capabilities over HTTP. In Windows Azure, you don’t need to do any of this, since it is done by Azure’s Table service. However, it is helpful to understand how the various components fit together.
Now, let’s explore how to write client-side code for ADO.NET Data Services.
Consuming Data Services
If you chose to, you could write your own library to parse the Atom/JSON results from ADO.NET Data Services and form the right URIs for queries. For non-.NET platforms, there are various open source libraries you can use. However, if you’re using .NET, you should be using the built-in support in .NET 3.5 SP1.
DataServiceContext and DataServiceQuery
DataServiceContext and DataServiceQuery form the core of ADO.NET Data Services’ support on the client side. If you’re familiar with the Entity Framework, you’ll be pleased to learn that these perform similar functions to the Entity Framework’s ObjectContext and ObjectQuery.
DataServiceContext is essentially used for state management. HTTP is stateless, and the server doesn’t “remember” the status of each client between updates. DataServiceContext layers on top of the HTTP stack to support change tracking. When you make changes to data in your client, the changes are accumulated in the DataServiceContext. They’re committed to the server when you call SaveChanges. DataServiceContext also controls conflict resolution and merge strategies. You can choose how to deal with conflicts when updating entities.
DataServiceQuery goes hand in hand with DataServiceContext. In fact, the only way you can get your hands on an instance of DataServiceQuery is through DataServiceContext. If you imagine DataServiceContext as the local representation of a service, DataServiceQuery would be analogous to a single query on that service. The two key methods on DataServiceQuery—CreateQuery<T> and Execute<T>—both take a URI (using the URI syntax discussed earlier) and return an IEnumerable to walk through the results.
Let’s look at how you can take the Cylon service you built earlier and run queries against it from client code:
- Add a Console project to your solution from the previous section. You can use any .NET project type. The console application is used in this examination, since that has the least amount of “cruft.” Be sure to pick .NET Framework 3.5 as the framework version.
- The client code must know to what type to deserialize the results. Add Cylon.cs from the other project to your console application. In a real-world application, you would put shared types in a class library that is shared by both your server-side and client-side pieces.
- Add references to System.Data.Services and System.Data.Services.Client.
- Add using statements at the top of your console app’s Main.cs to bring in the System.Data.Services.Client namespace, as well as the namespace in which Cylon.cs resides.
- Example 10-7 shows how you can execute a simple query that loops through all the Cylons in the service. You call up a new DataServiceContext instance and wrap it around the Cylon service. You create a DataServiceQuery from that instance and use it to loop through all the Cylons in the system.
Example 10-7. Client-side query
static void Main(string[] args)
{
// Replace 1096 below with the port your service is running on
DataServiceContext ctx = new DataServiceContext(
new Uri("http://localhost:1096/CylonService.svc"));
DataServiceQuery<Cylon> query = ctx.CreateQuery<Cylon>("/Cylons");
foreach (Cylon cylon in query)
{
Console.WriteLine(cylon.Name);
}
}
}If you run the code shown in Example 10-7 (ensure that the service is also running!), you should see the list of Cylons you entered. In the original code, you did not add support on the server side for creating or updating entities since, frankly, that’s a lot of code that you’ll never need to write with Windows Azure. However, if you had implemented IUpdateable, you could write the code shown in Example 10-8 to add entities.
Example 10-8. Adding an object
DataServiceContext ctx = new DataServiceContext(
new Uri("http://localhost:1096/CylonService.svc"));
ctx.AddObject("Cylons", new Cylon { ID = 9, Name = "Tyrol" });
ctx.SaveChanges();Updating and deleting entities works in a similar fashion. In both cases, you get the object you want to update/delete and use the DataServiceContext to perform the operation. Example 10-9 shows both scenarios. Note how you address the entity you want to update/delete using the primary key in the URL.
Example 10-9. Update and delete
DataServiceContext ctx = new DataServiceContext(
new Uri("http://localhost:1096/CylonService.svc"));
var query = ctx.Execute<Cylon>(
new Uri("/Cylons(1)", UriKind.Relative));
//Update Cavil's name
Cylon cavil = query.FirstOrDefault<Cylon>();
cavil.Name = "Cavil is evil!";
ctx.SaveChanges();
//Now delete Cavil
ctx.DeleteObject(cavil);
ctx.SaveChanges();Warning: This also highlights an issue with ADO.NET Data Services. You must load an object into the context before you can delete it. One workaround is to create an entity on the client side using the AttachTo method (this entity should have the same primary key as the entity you want to delete), calling DeleteObject and SaveChanges.
LINQ support
One of the biggest draws for ADO.NET Data Services is the ability to use LINQ queries. The LINQ-to-REST provider converts LINQ queries into URI requests. Note that this is only a subset of the full capabilities of LINQ. Operations such as GroupBy and Count are disallowed because these aggregate operations have no URI equivalent.
Example 10-10 shows how you could write a LINQ query to query for a specific Cylon differently. Note that, to make this work, you must use the Add Service Reference dialog to generate a strongly typed DataServiceContext.
Example 10-10. LINQ support
var ctx = new CylonDataModel(new Uri("[b][url="http://localhost:1096/CylonService.svc%22%29"]http://localhost:1096/CylonService.svc")[/url][/b]);
var query = from c in ctx.Cylons where c.ID == 4 select c;
Learn more about this topic from Programming Windows Azure.
Learn the nuts and bolts of cloud computing with Windows Azure, Microsoft's new Internet services platform. Written by a key member of the product development team, Programming Windows Azure shows you how to build, deploy, host, and manage applications using Windows Azure's programming model and essential storage services.

Help





