Watch recordings of Brian's WCF RIA Services webinars:
This article is Part 3 of the series WCF RIA Services.
Overview
In Part 2, I covered the query capabilities of WCF RIA Services in a little more depth. In this article, I’ll focus on the updating side of things, which is a bit more complicated than just retrieving data because there is a lot more going on. I’ll be covering the basic coding patterns for updating data, as well as talking about what is going on behind the scenes. Before I get to that though, there is one more important aspect of querying that I need to cover that didn’t fit in the last article, or the WCF RIA Services black helicopters will come down and get me. :)
Download the starting point for this article from Part 2 here.
Download the completed code from this article here.
IQueryable<T> and the Magic of Expression Trees
Back in Part 1, you defined a domain service method that looked like this:
public IQueryable<Task> GetTasks()
{
return this.ObjectContext.Tasks;
}
Now if you stare straight at this method, you would probably convince yourself that it is always going to return all of the Tasks in the database every time you execute it. However, if you tilt your head to the side just right, you’ll be able to see what it really does. Go ahead, I’ll wait… See it?
OK just kidding. To understand why this does not always return all tasks, you need to understand a little about expression trees and deferred execution. When GetTasks gets called, it is not actually executing a query at all. It is just forming an expression tree and returning that as an IQueryable<T> that describes what could be returned from this method. In this case, it could potentially return all tasks in the Tasks table. The expression tree that describes that is what gets returned to the client. The execution of actually running the underlying query is deferred until some future point when you actually try to act on the collection that the expression tree represents. Whenever you see an IQueryable<T>, remind yourself that it is not necessarily a collection yet – it is an expression tree that, when evaluated, will return a collection. But that expression tree can be modified by the receiver after returning it, and that can change the results that populate the collection when it gets evaluated.
The expression tree that is sent to the client can be modified before actually executing it. For example, suppose I execute the following code on the client.
TasksDomainContext context = new TasksDomainContext();
taskDataGrid.ItemsSource = context.Tasks;
EntityQuery<Task> query = context.GetTasksQuery();
LoadOperation<Task> loadOp = context.Load(query.Where(t=>t.TaskId == 1));
In line 2, I am binding to the Tasks collection on the domain context, which is currently empty because I just constructed the domain context. Then I get an EntityQuery<Task> from the context. This still doesn’t execute anything on the server, but the EntityQuery<T> lets me shape the expression tree based on what the target server method can execute (all tasks). When I call Load on the domain context, notice that I am passing a modified expression tree that includes a Where filter to only return a single task based on its ID. If you make a modification to the query client side like this, that expression tree is sent to the server when the async load operation executes, and that expression tree is applied to the actual expression tree that executes to retrieve rows. That means that not only won’t all rows be returned from the server to the client (saving bandwidth), but they won’t even be returned from the database when the query runs through the Entity Framework ObjectContext on the server side. You can convince yourself of this by firing up SQL Profiler and look at the query that runs on the database server. It includes a where clause for the TaskId column, as specified on the client side, without needing to explicitly declare a separate method that can do the filtering itself on the server side. Is that cool or what!?
DomainContext Caching and Change Tracking
OK, with that out of the way, lets switch focus to updating data. Before I start walking through some code, its important to understand a few concepts about the way WCF RIA Services works. Behind the scenes of the domain context, there is a lot more going on that just proxying calls to the server. Any entities or collections of entities you retrieve are cached through the domain context on the client side. That is why in the code in the previous section I could just bind the data grid to the Tasks collection on the domain context before executing the query. That collection will fire change events and update the UI when the cached entities change, at the point where the async load completes. In addition to caching the entities, the domain context maintains change tracking information about the cached entities so it knows if any have been modified, deleted, or added.
So instead of explicitly calling the server every time you want to change an object, you can make a bunch of changes to a bunch of objects and those will all be cached and tracked by the domain context. Then when you want to get those changes persisted on the server, you call SubmitChanges.
Step 1: Add update methods to your domain service
In Part 1 I walked you through the wizard for adding a domain service to the web project. At that point in the interest of keeping it as simple as possible, I didn’t have you do an important step for real apps – enable editing. When you add your domain services, you should check the box next to each entity that you expect to change at all on the client side:
When you check this box, the following additional methods are added to the domain service, which you can also easily add by hand:
public void InsertTask(Task task)
{
if ((task.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(task, EntityState.Added);
}
else
{
this.ObjectContext.Tasks.AddObject(task);
}
}
public void UpdateTask(Task currentTask)
{
this.ObjectContext.Tasks.AttachAsModified(currentTask, this.ChangeSet.GetOriginal(currentTask));
}
public void DeleteTask(Task task)
{
if ((task.EntityState == EntityState.Detached))
{
this.ObjectContext.Tasks.Attach(task);
}
this.ObjectContext.Tasks.DeleteObject(task);
}
These methods are simple wrappers around the appropriate update operations on the entity framework object context.
Step 2: Add some UI to invoke an add operation
Next you need something in the UI to invoke an update or add type of operation. Again, the pattern for update, add, and delete is pretty much the same – make the modification to the collection of objects exposed by the domain context, and call SubmitChanges. To keep it simple, you can just add another couple buttons to the top of the existing MainPage.xaml. Name the two buttons addTaskButton and saveChangesButton, set their Content property to Add Task and Save Changes respectively. Add button click handlers for both as well.
<Button Name="addTaskButton" Content="Add Task" Click="addTaskButton_Click" .../>
<Button Name="saveChangesButton" Content="Save Changes" Click="saveChangesButton_Click" .../>
Step 3: Create a new Task, add it to the domain context, and submit changes
Add the following code to the event handlers, as well as adding a member variable to contain the domain context:
TasksDomainContext context = new TasksDomainContext();
private void addTaskButton_Click(object sender, RoutedEventArgs e)
{
taskDataGrid.ItemsSource = context.Tasks;
context.Load(context.GetTasksQuery());
Task newTask = new Task
{
TaskName = "Deploy app",
Description = "Deploy app to all servers in data center",
StartDate = DateTime.Today,
EndDate = DateTime.Today + TimeSpan.FromDays(7)
};
context.Tasks.Add(newTask);
}
private void saveChangesButton_Click(object sender, RoutedEventArgs e)
{
context.SubmitChanges();
}
First, the code adds a member variable to the view for the domain context. As I mentioned earlier, when you are making changes to entities, you need to keep the context around long enough to submit the changes. In this case, I separated the add from the submit so you can see the effects on the client side. When you click the Add Task button, the addTaskButton_Click handler first replaces the ItemsSource for the DataGrid to replace the DomainDataSource that was hooked up in Part 1 (this is just to keep the code isolated for this article). It then calls Load again to get this domain context populated with the entities from the back end. Then it creates and populates a new Task entity and adds it to the context’s Tasks collection. That change will be seen immediately by the UI because the underlying collection implements INotifyCollectionChanged. However, the change is just cached client side, so until you call SubmitChanges, the new entity is not sent to the back end. I had you separate that out to a separate method so you can see this in the UI.
When you click Add Task, you should see the new entity show up in the DataGrid, but its TaskId is initially set to zero. After you click the Save Changes button and SubmitChanges is called, you will see the TaskId update. That is because when SubmitChanges is called, the domain context makes an asynchronous call to the server, sending only those entities that have actually changed (modified, added, or deleted). After that, if the entity is modified server side, such as setting the TaskId at the DB level, the updated entity is returned to the client and is merged with the cached copy, making it so the one in the Tasks collection is current with what the back end has. Because the entity has changed, and it implements the INotifyPropertyChanged interface, the UI updates.
DomainContext’s Async API
As mentioned in the previous parts, the Load and SubmitChanges API of the domain context execute asynchronously, meaning they don’t block the calling thread. They grab a thread from the thread pool behind the scenes, go make the calls to the server in the background, and when those calls complete, the work is automatically marshalled back to the UI thread to modify the entity collections and update the UI.
That is all smooth and effortless if all goes well. But the real world is not so perfect. You could have a connection burp, some knucklehead could have messed up the connection string, or you could have concurrency conflicts on the back end. Additionally, you may need to know when the operation completes so that you can do whatever is next in your workflow. To address these scenarios, you have two options: use the return type or hook up a callback that will be called when the operation is complete.
The first option is to work with the return value from these async methods. Load returns a LoadOperation, and SubmitChanges returns a SubmitOperation. These both derive from a common base class OperationBase, and they offer a bunch of information about the operation that you can write code against while it is running or after it is completed. They publish a Completed event when the operation completes, so you can subscribe to that to be notified when it is done. They expose any errors that happened and various other flags and properties you can check for results.
As an alternative to subscribing to the Completed event on the return value, you can instead call an overload of the Load or SubmitChanges operations that takes an Action<LoadOperation> or Action<SubmitOperation>, respectively. You pass in a callback reference and the method will be called when the operation is complete. Otherwise, the argument is the same type as the return type, so the same information is available to your code at that point.
Summary
That’s all there is to it. Make changes to the entity collection on the domain context, and call SubmitChanges. Implement methods in the domain service that take those changes and push them to storage. There is a bit more to talk about with transactions and concurrency conflicts, but I’ll have to cover those in a future episode for space considerations. Next time I’ll talk about how all this fits into the popular MVVM pattern.
Download the completed code from this article here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net.