Cover

ADO.NET Data Services 1.5 Feature: Projections

September 9, 2009
No Comments.

If you’ve been following my blog, you should know that I am keeping a pretty close watch on ADO.NET Data Services. The team recently released a second CTP of the new version with some interesting features. This CTP has some pretty compelling additions, but I am going ot focus on one in particular.

I’ve been teaching and using ADO.NET Data Services for a long time and I like showing off exposing a LINQ-based provider (Entity Framework, NHibernate or others) to a Silverlight application. While ADO.NET Data Services does expose its API through a REST API, the magic for me is in its use in Silverlight. In case you haven’t been following along, using the Silverlight client you can issue a LINQ query through the Silverlight client (though in fairness, the full power of LINQ is not supported in the client):

var qry = (from g in ctx.Games
           where g.Genre.Name == "Shooter"
           orderby g.ReleaseDate
           select g) as DataServiceQuery<Game>;

qry.BeginExecute(new AsyncCallback(r =>
{
  try
  {
    theList.ItemsSource = qry.EndExecute(r).ToList();
  }
  catch (Exception ex)
  {
    // NOOP
  }
}), null);

This is a powerful feature so that (unlike web services) developers can use a looser service definition to define their data stack. Let the developer who needs data be able to sort, filter and shape that data as necessary.

The last part of that phrase is “shape” on purpose. When I first teach this technology, I am often asked “what sort of shaping do you mean?” In ADO.NET Data Services 1.0, you could shape it by returning a hierarchy of data using the Expand method in the LINQ query. For example, we can return the game data with the Genre entity attached to every game like so:

var qry = (from g in ctx.Games
                        .Expand("Genre")
           where g.Genre.Name == "Shooter"
           orderby g.ReleaseDate
           select g) as DataServiceQuery<Game>;

qry.BeginExecute(new AsyncCallback(r =>
{
  try
  {
    theList.ItemsSource = qry.EndExecute(r).ToList();
  }
  catch (Exception ex)
  {
    // NOOP
  }
}), null);

But this level of shaping is powerful but limited in scope. This is where ADO.NET Data Services 1.5 comes up trumps. In this new CTP we can now do projections to retrieve specific data. For example:

var qry = (from g in ctx.Games
           where g.Genre.Name == "Shooter"
           orderby g.ReleaseDate
           select new
           {
             Name = g.Name,
             ReleaseDate = g.ReleaseDate.GetValueOrDefault(),
           }) as DataServiceQuery;

This query creates a new anonymous type that has the two values we want instead of returning the entire entity. In any other .NET project this would be fine, but for Silverlight we can’t use the anonymous type for data binding (for security reasons). So like other anonymous type uses in Silverlight, you must project them into a known type.  A type like that is pretty easy to cruft up if you need:

var qry = (from g in ctx.Games
           where g.Genre.Name == "Shooter"
           orderby g.ReleaseDate
           select new GameInfo()
           {
             Name = g.Name,
             ReleaseDate = g.ReleaseDate.GetValueOrDefault()
           }) as DataServiceQuery<GameInfo>;

This is useful to no only limit the fields you return but also to shape the result.  For example we can use the projection to retrieve the name, release date and the genre’s name (normally a related object) like so:

var qry = (from g in ctx.Games
           where g.Genre.Name == "Shooter"
           orderby g.ReleaseDate
           select new GameInfo()
           {
             Name = g.Name,
             ReleaseDate = g.ReleaseDate.GetValueOrDefault(),
             GenreName = g.Genre.Name
           }) as DataServiceQuery<GameInfo>;

This is all well and good, but what about saving these projections? For the most part you should treat complex projections (like the one above) as read-only. But if you retrieve entities that are purely subsets of existing entities, then ADO.NET Data Services will handle it.  Though currently this is crashing in Silverlight (it is a CTP after all). I will update this if I find a way around the saving bug in the near future and post the demo at that time.

But in general this is an awesome new addition to the library that I hope will close some of the use-cases that forced me to write Service Operations in the past.

What do you think?