Server Driven Paging II , Implementing a Smart Auto-Paging Enumerable

  1. Download Sample Application
  2. What is this ?
  3. How do I use this in my applications ?
  4. Cool, I am also interested in learning how this was built

2. What is this ?

         An “Auto-paging Enumerable” is an IEnumerable implementation that allows the application to
        automatically fetch the next page of results if the current page of results is enumerated and the application wants more.
         By “Smart”, we mean that we only load the next page when the current page runs out of results.We don’t load a page into memory 
        unless the  application needs it.

3.How do I use this in my applications ?

        Add the “ServerPagedQueryExtensions.cs” file into your application and use one of the following patterns.

  1. Binding to User Interface controls

    XAML Code for WPF Listbox:

     <ListBox DisplayMemberPath="Name" Name="lbCustomers" />
    

    C# code to bind the collection to the listbox:

     CLRProvider Context = new CLRProvider(new Uri("https://localhost:60901/DataService.svc/"));
    //Extension method on DataServiceQuery<T>
    var pagedCustomerRootQuery = (from c in Context.Customers
                                    select new Customer()
                                    {
                                        Name = c.Name
                                    }) as DataServiceQuery<Customer>;
    lbCustomers.ItemsSource = pagedCustomerRootQuery.Page(Context, maxItemsCount);
    
  2. With anonymous types in projections

     CLRProvider Context = new CLRProvider(new Uri("https://localhost:60901/DataService.svc/"));
    
    short maxItemsCount = 6;
    //Factory method 
    var customerPaged = ServerPagedEnumerableFactory.Create(
        from c in Context.Customers
        select new
        {
            Name = c.Name
        }, Context, maxItemsCount);
    
  3. In non-user interface backend code

     foreach (Customer instance in Context.Customers.Page(Context, maxItemsCount))
    {
        //Do something with instance variable here
    }
    

4. How this was built

At the root of this implementation is the IPagedEnumerator<T> interface which provides a contract for an IEnumerator<T> to signal that it has run out of elements
in the page.

 /// <summary>
/// Provides support for on-demand loading of the next page of results as soon as the current page of results is exhausted
/// </summary>
/// <typeparam name="TEntity">The type of the enumeration</typeparam>
public interface IPagedEnumerator<TEntity> : IEnumerator<TEntity>
{
    /// <summary>
    /// Provides access to the complete set of results
    /// </summary>
    List<TEntity> CompleteSet { get; }
    /// <summary>
    /// Signals the enumerator to move to the next page of results
    /// </summary>
    Func<IEnumerable<TEntity>> GetNextPage { get; set; }
}

The ServerPagedEnumerator<TEntity> type implements this interface and calls the GetNextPage method when it has run out of elements in the current page.

 public bool MoveNext()
{
    currentIndex++;
    if (SourceList.Count <= currentIndex && GetNextPage != null)
    {
        SourceList.AddRange(GetNextPage());
    }
    return SourceList.Count > currentIndex;
}
 /// <summary>
/// This class is an IEnumerable&lt;<typeparamref name="TEntity"/>&gt; which signals when the current page of results have been enumerated.
/// Developers can hook into the PageCompleted handler to return the next page of results
/// </summary>
/// <typeparam name="TEntity">The type of the collection</typeparam>
public class ServerPagedEnumerator<TEntity> : IPagedEnumerator<TEntity> {

The way we inject the ServerPagedEnumerator into the foreach is by implementing an IEnumerable that fetches the next page of results, when GetNextPage is called, by using the DataServiceContext and continuation tokens.
You can replace this with your implementation of paging which uses client-driven paging or some other custom scheme.

 /// <summary>
/// This class automatically fetches the next page in an Entity Set once the current page is enumerated completely
/// </summary>
/// <typeparam name="TEntity">The entity type of the request</typeparam>
public class ServerPagedEnumerable<TEntity> : IEnumerable<TEntity>
{
  /// <summary>
/// The Enumerator which calls a function whenever the current page of results are exhausted
/// </summary>
ServerPagedEnumerator<TEntity> ServerPagedEnumerator;
//Hook up to listen to the GetNextPage call so that we can get the next page when the current page of results are enumerated.
ServerPagedEnumerator.GetNextPage = GetNextPage;
 public IEnumerator<TEntity> GetEnumerator()
{
    return ServerPagedEnumerator;
}
 /// <summary>
/// Gets the next page in the Entity Set if the Continuation token points to a valid page.If 
/// not, returns an empty collection
/// </summary>
/// <returns>The next page of results</returns>
private IEnumerable<TEntity> GetNextPage()
{
    //If the continuation token is not null, we get the next page of results
    if (continuationToken != null)
    {
        return ExecuteQuery(continuationToken);
    }
    return new List<TEntity>();
}
 /// <summary>
/// Follows the DataServiceQueryContinuation&lt;<typeparamref name="TEntity"/>&gt; and returns the page of results while updating the continuation token.
/// </summary>
/// <param name="QueryContinuation">The Continuation token which represents the next page of the Entity Set</param>
/// <returns>The results of the query</returns>
private IEnumerable<TEntity> ExecuteQuery(DataServiceQueryContinuation<TEntity> QueryContinuation)
{
    QueryOperationResponse<TEntity> queryOperationResponse = contextInstance.Execute(QueryContinuation) as QueryOperationResponse<TEntity>;
    List<TEntity> responseList = queryOperationResponse.ToList();
    continuationToken = queryOperationResponse.GetContinuation();
    return responseList;
}

In the end, we tie everything together by creating an extension method on DataServiceQuery<T> so that we can use the .Page() extension method to provide an easy-to-use API.

 public static IEnumerable<TEntity> Page<TEntity>(this DataServiceQuery<TEntity> query, DataServiceContext ContextInstance)
{
    return new ServerPagedEnumerable<TEntity>(query, ContextInstance);
}