This is the first in a two-part article series on the WCF RIA Services Domain Context.
This article series is accompanied by source code, which can be downloaded here.
Introduction
A lot of business applications that are being developed in Silverlight today are built with the help of WCF RIA Services. This should come as no surprise, as it’s a really powerful, extensible framework which provides us with a lot of features out of the box (validation, authentication, authorization, …) that otherwise would require quite a lot of custom code, workarounds & plumbing. In WCF RIA Services, you’re going to be working with a client-side Domain Context instance. This article will look into a few strategies on working with this Domain Context, and is accompanied by a demo & source code, which you can download here.
But let’s start with a short introduction on what a Domain Context actually is.
What is a Domain Context?
When you create a new Domain Service & build your project, you’ll notice a Domain Context class (a class inheriting DomainContext) has been generated for you on the client (in a Silverlight class library project if you’re using a WCF RIA Services class library, or straight into your Silverlight project): one for each Domain Service. If you create a Domain Service named MyDomainService, you’ll get a generated Domain Context on your client named MyDomainContext, which inherits from the DomainContext class. It contains query operations to fetch data, collections of the entities you’re exposing through the Domain Service, submit and reject operations, … It provides a lot of functionality to help you with change tracking & interacting with the underlying Domain Service.
Those of you who are familiar with an object relation mapper (ORM), like the Entity Framework, will feel right at home: working with the WCF RIA Services Domain Context is quite similar to working with the Object Context you get when using the Entity Framework: you load data into the Context, the Context has collections of Entities you’re exposing through your Domain Service(s), it allows for change tracking on these entities, and you get your typical submit & reject-operations. Of course, you’re working in an asynchronous, service oriented environment when using WCF RIA Services: what actually happens when you submit the changes on your Domain Context is that that same context is rebuilt on the server side, and submitted once completely built.
As an example, we’ll assume we’ve got an Entity Model with a Category & Book object (cfr John Papa’s Bookstore Club example). A Book belongs to a Category, so one Category can have multiple Book objects: we’ve got a Navigation Property Books on our Category. When you create a Domain Service, BookDomainService, and select these 2 entities, WCF RIA Services will generate all the CRUD operations for you. Note that this “generation” is done once; it’s nothing more than a bit of help to get you started (however: in real-life scenarios, take care of this: you do not want operations to be exposed through your services which aren’t used) – this is different from the generation of the client-side Domain Context, which happens on each build.
In your Silverlight application, you’ve now got a BookDomainContext. This Domain Context contains a list of Books and a list of Categories. Each Category from that list has a list, Books (eg: the NavigationProperty from the EntityModel).
Image we load all the categories & their books. We change 1 book, add 1 category and add 1 new book to that category. When you submit these changes, the UpdateBook method will get executed once, the InsertCategory & InsertBook methods will get executed once as well, and only after that, the server-side Object Context is submitted, resulting in the necessary queries to your DB. You can easily check this behavior by overriding the Submit method on your Domain Service: if you set a breakpoint on it, you’ll notice it will only get hit after the CUD operations have been executed.
So, in essence, what we’ve got is a client-side version of a server-side Object Context. Needless to say: this approach became very popular in a short amount of time, and WCF RIA Services is being used for large, line of business applications all over the world.
However: when you choose this approach, you should be aware of how the DomainContext works and what the different strategies for using it are. The explanation above should already give you an idea of its inner workings, but when starting a new project, an important question will always rise up: should I use one or more instances of my Domain Context(s)?
Instance strategies for your Domain Context
In essence, you’ve got to choose between 3 different approaches: working with one general Domain Context instance, shared across all your ViewModels, working with a specific Domain Context instance on each ViewModel, or a mix of the previous 2 approaches, where some ViewModels share the same Domain Context instance, and others have their own. We will now look into these approaches, list a few reasons to choose (or reject) a certain approach, and look into the issues that you might face with each approach.
First things first though: regardless of the chosen approach, it’s always a good idea to think about what should go in each Domain Service. Just a few months ago, we didn’t have a lot of choice: using the same entity in two different Domain Services wasn’t supported and resulted in an error. Since SP1, this has been solved: you can now easily share the same entity in different Domain Services (and thus have it tracked by different Domain Contexts). So that would be the first tip: split up your domain services depending on the functionality a certain module of your application requires. This not only keeps them much easier to maintain, it also allows for greater abstraction & code re-use.
Strategy 1: one Domain Context instance for each ViewModel
Choosing the right approach will typically depend on the (functional) requirements of your application. First of all, we’ll look into the “one Domain Context instance for each ViewModel” approach. This approach is used in a lot of examples you can find online, and it looks as the right way to go for a lot of applications.
The advantages aren’t hard to see: as a Domain Context cannot do partial submits, a submit operation on your context will submit all the changed entities on that context at once. One instance per ViewModel allows you to easily submit only the changes that have been made to your data used in that ViewModel. Next to that, if you’ve got different VM’s working on different sets of data of the same type, you can keep on working straight on the EntitySets (or CollectionViews on those entity sets) of your Domain Context instance. For example: in one ViewModel you could load all new Books in the Book collection on your Domain Context instance, in another ViewModel you could load all Books from a certain author in that collection on your Domain Context (of course, there are other ways to achieve the same – for example, working with ICollectionViews and filtering the data on the client or server, using filters on your EntityQuery, …). The essence is: your Domain Context isn’t shared, so it’s kept within the boundaries of that ViewModel: whatever you do on it, it’s not reflected in other ViewModels.
This is the approach we took when creating our 360 Review Application, of which I wrote a white paper a few weeks ago. Our main concerns here were: allow the user to navigate wherever he wants without requiring him to save changes before leaving an active screen (eg: a user could easily have 4, 5 active screens with unsubmitted changes), and only submit the data that is needed to the server per screen, not for all screens at once.
However, this does pose a problem: keeping your Domain Contexts in sync across different ViewModels. For example, imagine two parts of an application, two different ViewModels working on the same (or rather, related) data. A simple example could be: in one ViewModel, for example the one used in a sort of Dashboard screen in your application, you get an overview of all the Books in your DB. In another ViewModel, you’re allowing the user to add, edit, delete, … one of those books. Image you’re adding a new Book, submit the changes to server, and go back to the other view: the new Book won’t show up there, as, at the moment, it’s not being tracked by that DomainContext instance. You could of course automatically refetch the Employee collection whenever a user navigates to the dashboard View (and underlying ViewModel), but that’s not too good, performance and bandwidth wise.
How can we handle this? Well, out of the box, you can’t: there’s no way to automatically “sync” different Domain Context instances. It will require some custom code, and how do this, again, depends on your application requirements.
I’ve created an example application to show this behavior (note: the example applications uses Laurent Bugnions’ MVVM Light Toolkit - powerful & really lightweight, check it out if you haven’t done so yet, and John Papa’s Bookstore Club database).
In this application, you can see 2 views on the same page, of which the ViewModels have their own Domain Context instance. Both Views show a list of all the books in the database – they are bound to an EntitySet<Book> (Context.Books), which is loaded in the VM’s constructor. When you add a Book on the second view, a new Book (with some default values filled out) is added to the Domain Context, and the Domain Context is saved by calling its SubmitChanges() method. You’ll notice that the book is correctly added to the end of the book ListBox on the right, but it’s missing in the ListBox on the left.
So, how do we make sure both Domain Contexts are kept in sync? One possible approach would be to manually redo the changes you’ve made on one instance on the other. As you might know, entities can be attached to a context in code: what you can do is send a message when the book has been submitted, containing the entity you’ve just added, to a ViewModel that’s handling entities of the same type. The receiving ViewModel would then attach the Entity you pass in to its Context, resulting in 2 synced Domain Context instances. You do need to create a copy of the Entity to attach, as an Entity can only be tracked by one Domain Context instance at the same time. In our example, the code would look like this:
Submit the book in the ViewModel on the right, and create a new message:
private void AddBookAndAttachExecution()
{
// add the book to the context, and save changes
Context.Books.Add(NewBook);
var submitOp = Context.SubmitChanges();
submitOp.Completed += (send, args) =>
{
if (submitOp.HasError == false)
{
// send a message telling any registered VM's a book has been added
Messenger.Default.Send<BookAddedMessage>(
new BookAddedMessage(NewBook));
// create a new book with default values
NewBook = new Book()
{
ASIN = "123",
AddedDate = DateTime.Now,
Description = "Default description",
Author = "Author",
Title = "Title",
PublishDate = DateTime.Now,
CategoryID = 1,
MemberID = 1
};
}
};
}
The message constructor creates a copy of the Book:
public class BookAddedMessage
{
public Book Book;
public BookAddedMessage(Book book)
{
// create a COPY of the book that's passed in. This one
// is attached to another context, and you cannot attach the
// same entity to two domain context instances.
this.Book = new Book()
{
BookID = book.BookID,
ASIN = book.ASIN,
AddedDate = book.AddedDate,
Description = book.Description,
Author = book.Author,
Title = book.Title,
PublishDate = book.PublishDate,
CategoryID = book.CategoryID,
MemberID = book.MemberID
};
}
}
The message recipient, in our example: the ViewModel on the left, attaches the Book to its context:
private void BookAddedMessageReceived(BookAddedMessage msg)
{
// attach the newly added book (in an unmodified state)
Context.Books.Attach(msg.Book);
}
This results in the following (Domain Context instances are in sync):
The advantage of this approach is that it doesn’t require an extra load operation to refetch the list of books from the server – the Book is submitted once, and all the rest is handled on the client (you might want to write an extension method to copy Entities to make your life easier). However, you’ll quickly run into the fact that designing your application like this will require a lot of maintenance: in this example, we’re only talking about 1 list of books, but in a real-life application, you’ll typically be handling a multitude of possible objects & child objects, which could be added, updated or removed, on a multitude of ViewModels with a multitude of Domain Context instances.
Another possible approach is to send messages to the related ViewModels, telling them that they have to refresh themselves. A refresh would re-load the data from the server, in this case: reload the list of books. In the example application, that would look like this:
Submit the book in the ViewModel on the right, and create a new message:
private void AddBookAndRefreshExecution()
{
// add the book to the context, and save changes
Context.Books.Add(NewBook);
var submitOp = Context.SubmitChanges();
submitOp.Completed += (send, args) =>
{
if (submitOp.HasError == false)
{
// send a message telling any registered VM's to refresh
Messenger.Default.Send<RefreshBooksMessage>(
new RefreshBooksMessage());
// create a new book with default values
NewBook = new Book()
{
ASIN = "123",
AddedDate = DateTime.Now,
Description = "Default description",
Author = "Author",
Title = "Title",
PublishDate = DateTime.Now,
CategoryID = 1,
MemberID = 1
};
}
};
}
The message recipient, in our example: the ViewModel on the left, refetches the list of books:
private void RefreshBooksMessageReceived(RefreshBooksMessage msg)
{
// reload the data
LoadData();
}
private void LoadData()
{
Context.Load<Book>(Context.GetBooksQuery());
}
The disadvantage of this approach is that it comes with higher bandwidth usage: you’re refetching data from the server. But the advantage comes in the form of simplicity and lower maintenance: instead of having to pass through all the changed objects & child objects, you simply send one message: refresh entities of this type. Depending on how you wrote your EntityQueries, the child objects (when applicable) will be refetched if they need to be.
That’s all for the first strategy. Stay put for the second and third approach in the next part of this article series!
About the author
Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Portugal and Belgium, or on BESUG events (the Belgian Silverlight User Group). Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/, and of course he can also be found on Twitter @KevinDockx