WCF Ria Services For Real

MiniTutorialLogo

In my previous post I discussed creating the database and tables for the Silverlight HVP configuration data.  All that was great, and worked just dandy until it was time to get the data from the database server to the application running on the client.

“But,” I thought, “How hard can it be?”  I’ve done a few mini-tutorials… should be straight-forward… And it was… sorta.

Getting Going

The steps are pretty straight forward:

  • Create an entity data model that corresponds to the tables
  • Create a Domain Service to handle transport and creation of classes
  • Modify the queries to select the data you want

One of the strengths of WCF RIA Services is also one of its most confusing aspects: there are many good designs to accomplish the same thing.  I’m a big believer in doing things in the most intuitive and obvious way and then looking to optimize or improve if and when it proves to be needed.

In this case, I had chosen to simplify the persistence of the various fields in my business objects as simple types in the database. For example, here is the Topics class and its base class:

namespace SilverlightHVP.ViewModel
{
   public class Listable
   {
      public string TextToDisplay { get; set; }
      public ImageSource Image { get; set;}
      public int ID { get; set; }
   }

   public class Topic : Listable
   {
      public TimeSpan TopicOffset { get; set; }
   }
}
The Topic class and Listable class are declared in Topic.cs and Listable.cs respectively

Notice that Image is of type ImageSource and TopicOffset of type Timespan.  Here is how they are declared in the database:

TopicsAndListablesDB

Each record has a unique ID, and Topic’s MyListableID is a foreign key into Listable, linking the two into, effectively, a single object.

It is an interesting aside that normalizing the Topics, Items and Links tables called for factoring out the same fields into a new table as were factored out into a base class when creating the types. In many ways this makes sense, as in both normalization and refactoring, the goal is to eliminate duplication.

TopicsView

SELECT     Listables.ID AS ListablesID,
           Listables.TextToDisplay,
           Listables.ImageSource,
           Topics.ID AS TopicsID,
           Topics.Offset,
           Topics.MyItem,
           Topics.MyListableID
FROM       Listables
           INNER JOIN Topics
           ON dbo.Listables.ID = dbo.Topics.MyListableID

Returning to my original point, however, while the Listable type stores its Image field as an ImageSource, the ImageSource field in the database is a string. Similarly, while the Topic type stores the Offset as a TimeSpan, the value is stored in the database as an integer (the number of seconds).

There are numerous ways to resolve these differences, among them:

  • Store the values in the database as they are stored in the objects
  • Create conversions or a converter class
  • Retrieve objects that represent the database fields, and convert the values when initializing the ViewModel objects

If I were starting from scratch, and if I knew that the only way these values would ever be stored is in the database, I could make a good argument for the idea of storing the values in the database as the same types used in the objects.  That is not how this evolved however, and, in fact, if/when we decide to persist the objects to Xaml before sending them to the db, any work that was done to create an isomorphic mapping to the database would be wasted.

Thus, again, I let the design evolve organically, even if that doesn’t leave me, from time to time, with what looks like the optimal (or at least text-book) way of doing things.

Creating the Entity Data Model

CreateNewEDM
EDMDiagram
GenerateDomainServiceClass

The mechanics of creating the Entity Data Model couldn’t be much easier.  Step 1 is to Add a New Item to the SilverlightHVP.Web project, of type ADO.NET Entity Data Model (found in the Data Templates).

When you click Add, Visual Studio will ask for your source, in this case you will pick Database. It will then allow you to create a Connection String to the configuration database.

Once the connection is established, you pick the tables and views you want in the Entity Data Model, and hey! Presto!  Visual Studio creates the model for you.

The relationships in the Entity Data Model map to the relationships in the database.  You are free to modify the entities, but it is simpler to let Visual Studio do the heavy lifting.

It is very important to build the SilverlightHVP.Web project at this point, before going further.

With the project built, you just click on Add New Item to SilverlighHVP.Web once more, and this time click on the Web template, and then on Domain Service Class, and give your new service a name (e.g., HVPConfigRiaDomainService)

This brings up the Add New Domain Service Class dialog, in which you pick the entities (from the Entity Data Model) you wish to access through your WCF Ria Service.  Since I want access to them all, but do not need to edit them, I’ll check all the Entities in the left column.

Hiding, inconspicuously in the lower left corner is an important CheckBox: Generate associated class for metadata.  Click that CheckBox and then click OK.

Visual Studio will place you in the editor, inside the HVPConfigRiaDomainService.cs file.  Here you will see the code that was generated to retrieve data from your tables.

The generated code can be modified to select the records you want, and to order the results as well, as shown in the next code listing.

using System.Linq;
using System.Web.DomainServices.Providers;
using System.Web.Ria;

namespace SilverlightHVP.Web
{

   [EnableClientAccess()]
   public class HVPConfigRiaService :
            LinqToEntitiesDomainService<yzf265_hvp_User>
   {
      public IQueryable<Item> GetItems()
      {
         return this.ObjectContext.Items;
      }

      public IQueryable<ItemsView> GetItemsViews( int SetID )
      {
         return this.ObjectContext.ItemsViews.Where(
            e => e.MySetID == SetID );
      }

      public IQueryable<Link> GetLinks()
      {
         return this.ObjectContext.Links;
      }

      public IQueryable<LinksView> GetLinksViews( int itemID )
      {
         return this.ObjectContext.LinksViews.Where(
            e => e.MyItem == itemID ).OrderBy( e => e.Offset );
      }

      public IQueryable<Listable> GetListables()
      {
         return this.ObjectContext.Listables;
      }

      public IQueryable<Topic> GetTopics()
      {
         return this.ObjectContext.Topics;
      }

      public IQueryable<TopicsView> GetTopicsViews( int itemID )
      {
         return this.ObjectContext.TopicsViews.Where(
            e => e.MyItem == itemID ).OrderBy( e => e.Offset );
      }
   }
}

Retrieving Items, Topics and Links

When the program begins we create a situation identical to that of when the user clicks on a link: we need to retrieve a new Set of items.  Remember that a Set consists of one or more Items (e.g., videos) and each Item has zero or more Topics and zero or more Links.  Each topic is tied to an offset in a given video, each Link is tied to a Set.

The State constructor takes an integer parameter indicating which Set to obtain (at the moment, the shell creates the State object and passes in the value 0).

The constructor calls the GetNewState method,

 private HVPConfigRiaContext itemsContext;
 private void GetNewSet( int setID )
 {
    if ( setID < 0 )
    {
       System.Windows.MessageBox.Show( "That link is not yet implemented" );
       return;
    }
    currentSet = new Set();
    itemsContext = new HVPConfigRiaContext();
    try
    {
      var LoadItems = itemsContext.Load( itemsContext.GetItemsViewsQuery(setID) );
      LoadItems.Completed += new EventHandler( LoadItemsCompleted );
    }
    catch
    {
       System.Windows.MessageBox.Show( "That link is not yet implemented" );
       return;
    }
 }

The heart of this method is within the try block, where we ask the Context object to load the GetItemsViewQuery we modified above. The object returned from a call to Load is of type LoadOperation<ItemsView> which we can use to set an event handler on the completion of the asynchronous Load.

When the Load completes, we’ll combine three activities into one:

  • Copying the database Items into ViewModel Items
  • Converting from strings and integers to URIs and Timespans
  • Encasing the Items into ItemHolder objects
private void LoadItemsCompleted( object sender, EventArgs e )
{
   var dbItems = itemsContext.ItemsViews;
   if ( dbItems.Count == 0 )
   {
      System.Windows.MessageBox.Show( "Unable to retrieve items from the database." );
      return;
   }

   ItemHolder holder = null;
   currentSet.ItemHolders = new ObservableCollection<ItemHolder>();
   foreach ( var dbItem in dbItems )
   {
      holder = new ItemHolder();
      var item = new Item( dbItem.ListablesID );
      item.TextToDisplay = dbItem.TextToDisplay;
      item.ItemUri = new Uri( dbItem.ItemUri.ToString() + "/manifest", UriKind.Absolute );
      item.Image = new BitmapImage( new Uri( dbItem.ImageSource, UriKind.Absolute ) );
      holder.theItem = item;
      currentSet.ItemHolders.Add( holder );
   }
   this.CurrentItem = currentSet.ItemHolders[ currentItemHolderOffset ].theItem;
   OnStateChanged();
}

The penultimate line of code shows the CurrentItem property of State being set within the Item object that is held by the ItemHOlder located at the currentItemHolderOffset offset.  That value is set to 0 initially, and updated as the current item changes.

Notice that the value is set by way of the property. The setter for the CurrentItem property does a bit of housekeeping,

private Item currentItem = null;
public Item CurrentItem
{
   get { return currentItem; }
   set
   {
      currentItem = value;
      SetCurrentItemHolderOffset();
      GetTopicsAndLinksForItem(currentItem.ID);
      OnCurrentItemChanged();
   }
}

Specifically, the setter updates the CurrentItemHOlderOffset value and, even more important, calls the LoadingTopics GetTopicsAndLinksForItem method, passing in the ItemID.

The Items (and the links) are then independently and asynchronously loaded from the database, much as the Item was.

Lazy Loading

This design allows for the topics and links of an item to be loaded to the client only as need and asynchronously, providing a more responsive UI that is not bogged down waiting for any of the list boxes to fill. Since the video is streamed in small bursts whose quality (size) is set in response to current bandwidth, the entire application should appear “snappy” even though three queries are returning data for the configuration.

I’m very happy to report that we are on track for version 1 to be ready in time for Mix, and we are already selecting the most important new features for versions 1.1 and 2.0.  For more information on this project, please take a look at the CodePlex site.

jessesig

This work is licensed under a Creative Commons license.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives and tagged , . Bookmark the permalink.

6 Responses to WCF Ria Services For Real

Comments are closed.