This article is compatible with the latest version of Silverlight.
Introduction
The last few weeks have been very exciting. At PDC 2009 Microsoft announced WCF RIA Services which is now the official name of .NET RIA Services. Their intention is to emphasize how close RIA Services and WCF is. However there were minor changes to the API it didn’t affect my previous article so you can still consider it valid.
In my last article I wrote about the basic concepts of WCF RIA Services and n-tier development. In this one we’ll talk about the features of WCF RIA Services in terms of creating complex queries.
Setting up WCF RIA Services
We know how to create simple queries against a custom layer. Now I’m going to use Entity Framework and I’ll create an Entity Data Model (EDM) for Northwind database using Visual Studio wizards. My EDM is very simple I’m using only two tables.
WCF RIA Services has a Visual Studio template and a related wizard which will help me generate my Domain Service class. I added a new item called Domain Service Class. Now Visual Studio pops up a dialog like this one:
Notice the different checkboxes. I enabled client access for this DomainService which will put the EnableClientAccessAttribute on top of my DomainService class. This will trigger a code generation process, and a client-side DomainContext class will be generated. Now my data access layer is Entity Framework provided, so as an ObjectContext I selected NorthwindEntities, and I also enabled both my entities to be published over RIA Services. By checking the last checkbox I generated metadata classes for my domain entities. (I’ll explain the roles of metadata classes later in the article.)
The following code is generated for me:
[EnableClientAccess()]
public class NorthwindDomainService : LinqToEntitiesDomainService<NorthwindEntities>
{
public IQueryable<Categories> GetCategories()
{
return this.ObjectContext.Categories;
}
public IQueryable<Products> GetProducts()
{
return this.Context.Products;
}
}
This time the base class of my NorthwindDomainService is not DomainService, it is LinqToEntitiesDomainService<T> which is basically a special domain service base class for Entity Framework. It publishes a ObjectContext property which provides you direct access the your ObjectContext instance. So you can write your query against this property.
After a simple build process code generation happens. Client-side objects are now created, so all we have to do now is to start writing queries.
Simple Query Model
My first query is simple. Get me all the products which belong to the ”Beverages” category:
NorthwindDomainContext ctx = new NorthwindDomainContext();
var query = from p in ctx.GetProductsQuery()
where p.Categories.CategoryName == "Beverages"
select p;
ctx.Load<Products>(query);
productDataGrid.ItemsSource = ctx.Products;
So my client-side context is my DomainContext instance which provides me all the features WCF RIA Services offers, like transparent asynchronous communication to the DomainService, client-side change tracking, etc...
To compose a query I have to use the GetProductsQuery method which returns an EntityQuery and uses the server side GetProducts method to serve as the base of the query. See how easy it was to navigate through associations? We don’t have this data (Categories.CategoryName) on the client side, my query has to be run on the server-side.
The Load() method triggers the actual call to the domain service. It’s an asynchronous operation. The Result will be loaded into the Products EntitySet.
See how easy that was? It was like writing a query directly against my EDM. But what if something bad happens, maybe an exception occurs. Our call to the domain service is asynchronous so try-catch won’t help me much.
Collecting information about our call
The return type of the Load method is a LoadOperation<T> which has many usefull properties to determine what happened during the call.
Most important properties of LoadOperation<T> class:
- HasError – Something bad happened during the call
- Error – Wraps the exception that may occurred during the call
- UserState – An object that can be passed by the caller for later use
- Entities – List of all the loaded entities
- EntityQuery – The composed query
- IsCompleted – Is the operation complete?
- IsCancelled – Is the operation cancelled?
- CanCancel – Is the process in a state where it can be cancelled?
One way you could go is to poll your LoadOperation object whether it „IsCompleted” or not. Another way I prefer much more is to use callback functions. Let the domain context tell us once the operation is done. One of the overloads of Load() method accepts an Action<LoadOperation<T>> delegate.
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
NorthwindDomainContext ctx = new NorthwindDomainContext();
var query = from p in ctx.GetProductsQuery()
where p.Categories.CategoryName == "Beverages"
select p;
ctx.Load<Products>(query,new Action<LoadOperation<Products>>(product_LoadCompleted), null);
productDataGrid.ItemsSource = ctx.Products;
}
private void product_LoadCompleted(LoadOperation<Products> operation)
{
if (operation.HasError)
{
ErrorWindow.CreateNew(operation.Error);
}
}
Now if something bad happens during the call (like we mistype the connection string on the server :) ) we can provide feedback:
Controlling data flow
Once we have our data on the client-side we can start working with it. But what if after I loaded Categories into my DomainContext I need the related products as well. It can’t be already on the client-side. If it would be there it would mean that probably all the entities I publish through WCF RIA Services are there too, which in some cases means that my whole database is on the client side. This unacceptable, so Microsoft choose not to include related entities when sending back data to the client. Fortunately I have the option to tell RIA Services that every time I ask for a specific entity give me back the associated entities I want as well. This is done through metadata classes.
The easiest way to tell something about a property or a class is done through attributes. But once you have your data model generated you cannot change the code. You can write additional code to it since your classes are probably partial classes but you can’t add an extra attribute to a generated property because a regeneration process would destroy all your changes. This is where metadata classes come into play.
Metadata classes are internal classes defined in the class for which you want to provide extra information. In these classes you specify the properties you want to decorate and apply attributes to them. You tell the parent class to use this metadata class through the MetadataType attribute. Now WCF RIA Services knows when you work with your class you’ll want special treatment based on your attributes in the metadata class.
In our case we want to tell RIA Services to include the related Products list every time a category is being loaded. Remember when we checked ”Generated associated metadata classes”? It created a NorthwindDomainService.metadata.cs file for us. Let’s see what’s inside:
[MetadataTypeAttribute(typeof(Categories.CategoriesMetadata))]
public partial class Categories
{
internal sealed class CategoriesMetadata
{
private CategoriesMetadata()
{
}
public int CategoryID;
public string CategoryName;
public string Description;
public EntityState EntityState;
public byte[] Picture;
public EntityCollection<Products> Products;
}
}
You can see that the Categories class is associated with the CategoriesMetadata class using the MetadataType attribute. Now what I have to do is to decorate the Products field in the CategoriesMetadata class and tell RIA Services to include this association into every query.
[Include]
public EntityCollection<Products> Products;
That’s it. Of course you’ll have to tell you data access layer as well to load the associated data from the database. In this case:
public IQueryable<Categories> GetCategories()
{
return this.Context.Categories.Include("Products");
}
However this works you cannot determine on the client side whether you want to include the list of products or not. What you can do to solve this issue is to modify the GetCategories domain operation like this:
public IQueryable<Categories> GetCategories(bool loadProducts)
{
if (loadProducts)
{
return this.Context.Categories.Include("Products");
}
else return this.ObjectContext.Categories;
}
Now you can pass a boolean value to determine whether you need the products as well or not.
The client side code will look like the following:
NorthwindDomainContext ctx = new NorthwindDomainContext();
//Pass true to load the realted products, false not to.
var query = ctx.GetCategoriesQuery(true);
ctx.Load<Categories>(query,
new Action<LoadOperation<Categories>>(categories_LoadCompleted), null);
Summary
As you’ve seen WCF RIA Services provides you a great deal of flexibility when writing queries and also provides different means to control and gather information about a specific call. When querying against a domain service we should not forget that associated entities might not be loaded as well. To solve this issue we can use metadata classes to tell RIA Services what it should include in the response and what is shouldn’t. In the next part we’ll see how to modify data using WCF RIA Services. I hope this helps.