I’ve been writing a lot of Silverlight applications as of late that make extensive use of the new .NET RIA Services framework. A common pattern that I’ve ran into is the initial loading of data when a new page in the application has been navigated to. Typically each page requires data from more than one table and as such results in my having to issue multiple Load() calls against my DomainContext. These calls all run in an async fashion with no guaranteed order of completion. In previous versions of RIA Services this meant that as a developer you needed to handle all of the multiplexing issues yourself, typically by creating some scheme for using the userState parameter in the load call to coordinate the de-multiplexing inside of the DomainContext’s Loaded event callback.

The July 2009 CTP of RIA Services changes all of that however. We’ve changed the API to allow each Load() call to specify it’s own callback that will only be called once and only in association with that one specific Load() call (that is, we handle the de-multiplexing for you now). The new Load API now looks like this (in it’s most verbose form):

public LoadOperation<TEntity> Load<TEntity>(EntityQuery<TEntity> query, MergeOption mergeOption,
                Action<LoadOperation<TEntity>> callback, object userState) where TEntity : Entity;

This means that you can now specify a per Load callback delegate that will get called as soon as that particular load call is done processing. Additionally, instead of returning VOID as we did in prior CTPs, we now return a LoadOperation class that represents the async load operation and it’s state. For those of you that prefer the eventing pattern over the callback pattern (or if your application has a situation where you need to have multiple listeners), this class comes with a handy dandy Completed event that you can sign up for. And it’s this handy dandy event that’s going to allow us to build a batch manager.

Per call notifications are great for solving the de-multiplexing issue, but typically there is also processing that needs to be performed once all of the data necessary for a page has been loaded. I found myself writing to track the number of outstanding load calls so I would know when to execute this final processing code. I’ve moved all that logic into a simple reusable batch manager class that is shown below. Using this batch manager you can issue as many Load() calls as you want and still receive a single aggregate callback when they have all completed, even if some of them fail along the way (more on that later).

using System;

using System.Collections.Generic;

using System.Windows.Ria.Data;

 

namespace BusinessApplication1

{

    /// <summary>

    /// Simple LoadOperation batch manager

    /// </summary>

    /// <remarks>

    /// This class allows you to start multiple load operations in parallel and still received a single

    /// 'complete' action callback once all work is done. Load operation failures are also aggregated

    /// and will be made available as part of the completion action callback.

    /// </remarks>

    public class DomainContextLoadBatch

    {

        private List<LoadOperation> _pendingOperations = new List<LoadOperation>();

        private List<LoadOperation> _failedOperations = new List<LoadOperation>();

        private Action<DomainContextLoadBatch> _completeAction;

 

        /// <summary>

        /// Expose the count of failed operations

        /// </summary>

        public int FailedOperationCount { get { return _failedOperations.Count; } }

 

        /// <summary>

        /// Expose an enumerator for all of the failed operations

        /// </summary>

        public IEnumerable<LoadOperation> FailedOperations { get { return _failedOperations; } }

 

        /// <summary>

        /// Public constructor

        /// </summary>

        /// <param name="completeAction"></param>

        public DomainContextLoadBatch(Action<DomainContextLoadBatch> completeAction)

        {

            this._completeAction = completeAction;

        }

 

        /// <summary>

        /// Used to add an operation to the batch

        /// </summary>

        /// <param name="loadOperation"></param>

        public void Add(LoadOperation loadOperation)

        {

            loadOperation.Completed += new EventHandler(loadOperation_Completed);

            _pendingOperations.Add(loadOperation);

        }

       

        /// <summary>

        /// Processes each operation as it completes and checks for errors

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void loadOperation_Completed(object sender, EventArgs e)

        {

            LoadOperation loadOperation = sender as LoadOperation;

            _pendingOperations.Remove(loadOperation);

            loadOperation.Completed -= new EventHandler(loadOperation_Completed);

 

            if (loadOperation.Error != null)

                _failedOperations.Add(loadOperation);

 

            CheckForComplete();

        }

 

        /// <summary>

        /// Called to check for all operations being complete and to fire our completed action

        /// </summary>

        private void CheckForComplete()

        {

            if (_pendingOperations.Count > 0)

                return;

 

            if (_completeAction != null)

            {

                _completeAction(this);

                _completeAction = null;

            }

        }

    }

}

 

The new Load API makes managing batched load calls fairly easy and as you can see there’s not a lot of code here. Additionally it doesn’t take much code to use the batch manager either. Here’s a sample code snippet of how you would use this in one of your pages.

private DomainContext1 domainContext = new DomainContext1();

public MainPage()

{

    InitializeComponent();

 

    DomainContextLoadBatch batch = new DomainContextLoadBatch(DataLoadComplete);

    batch.Add(domainContext.Load(domainContext.GetUsersQuery()));

    batch.Add(domainContext.Load(domainContext.GetUserRolesQuery()));

    batch.Add(domainContext.Load(domainContext.GetDisciplinesQuery()));

}

 

private void DataLoadComplete(DomainContextLoadBatch batch)

{

    if (batch.FailedOperationCount > 0)

    {

        // Do Error handling

    }

    else

    {

        // Fill out the UI with the data

    }

}

 

That’s it, typically just one extra line of code is all you need to manage your batched load calls. Notice also that we did not touch the actual Load() call itself, so the per call delegate as well as the userState parameter are still wide open for your application to use however it sees fit. A couple things to note about the DomainContextLoadBatch class:

  • Currently it only works for Load() calls. It would need to be extended if you run into a situation where you have multiple Submit() calls that you’d like to batch together (although, I’ve never actually ran into a situation that requires this yet).
  • There’s no thread management… that is this class is not thread safe. It assumes that all of your Load calls happen on the same thread and that is also the same thread that you call batch.Add() on.
  • The Complete callback gets called regardless of any errors, so be sure and check the FailedOperationCount property in your callback to deal with any failed load calls (the actual failed load calls are available using the FailedOperations enumeration).

There you go, a simple batch manager for .NET RIA Services. Until next time, happy querying (and batching)!