Paging and sorting are, de-facto, standard required implementations in any line of business (LOB) application and we, as Silverlight developers, all know how “boring” (I won’t categorize the feature as ‘complicated’) is the implementation, in special case if we’re dealing with a WCF Service and request its introduced after service design is completed.
Wouldn’t it be good to finally forget about paging and sorting and use a common reusable implementation? well’ you’ll be happy to know that thank to new classes added to RIA Service SP1 this is finally possible.

Let’s introduce the triad of classes used in our sample:

  • EntityList<T>  is an ObservableCollection backed by an EntitySet, its important to know that when an entity is removed from the list it also gets removed from associated EntitySet but it is not automatically added when a new entity is added to backed EntitySet.
  • DomainCollectionView<T> is a ICollectionView implementation, so it exposes everything you need to Page/Sort your data and accepts a CollectionViewLoader and a IEnumerable<T> as constructor parameters.
  • DomainCollectionViewLoader<T> is a CollectionViewLoader implementation that accepts the method to invoke to load data and another method to invoke when load operation completes.

BTW: In order to consume this types you need to reference: Microsoft.Windows.Data.DomainService.dll

RelationShip between these entities is quite simple: We expose the DomainCollectionView<T> as ICollectionView from our ViewModel providing an EntityList<T> mapped to our collection as data source and a DomainCollectionViewLoader<T> as “data collector” when new data need to be fetched from the server, since I don’t like to reinvent the wheel I’ve wrapped everything inside a PagerSorter<T> class:

public class PagerSorter<T> where T : Entity
{
private readonly DomainContext context;
private readonly EntityQuery<T> query;
private readonly DomainCollectionView<T> view;
private readonly DomainCollectionViewLoader<T> loader;
private readonly EntityList<T> source;

public PagerSorter(DomainContext context, EntityQuery<T> query, EntitySet<T> items, int pageSize)
{
this.context = context;
this.query = query;
this.source = new EntityList<T>(items);
this.loader = new DomainCollectionViewLoader<T>(this.OnLoad, this.OnLoadCompleted);
this.view = new DomainCollectionView<T>(this.loader, this.source);
using (this.view.DeferRefresh())
{
this.view.PageSize = pageSize;
this.view.MoveToFirstPage();
}
}

public DomainCollectionView<T> View
{
get { return this.view; }
}

private LoadOperation<T> OnLoad()
{
return this.context.Load(this.query.SortPageAndCount(this.view));
}

private void OnLoadCompleted(LoadOperation<T> op)
{
if (op.HasError)
{
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this.source.Source = op.Entities;
if (op.TotalEntityCount != -1) this.View.SetTotalItemCount(op.TotalEntityCount);
}
}
}

Note the SortPageAndCount extension method  applied to EntityQuery inside OnLoad method that, based on the state of the view passed to it it knows how to generate related Skip/Take/OrderBy LINQ queries to be passed to DomainService.

PageSorter can now be used inside a ViewModel we’re going to use to list all customers loaded from evergreen Northwind database, here’s the code:

public class CustomersViewModel : ViewModelBase
{
private readonly PagerSorter<Customers> pagerSorter;
private readonly MyContext context = new MyContext();
private readonly int pageSize = 5;

public CustomersViewModel()
{
if (!this.IsInDesignMode)
{
this.pageSize = 5;
this.pagerSorter = new PagerSorter<Customers>(this.context, this.context.GetCustomersQuery(), this.context.Customers, this.pageSize);
}

this.NextPageCommand = new RelayCommand(this.OnNextPage);
this.PreviousPageCommand = new RelayCommand(this.OnPreviousPage);
this.FirstPageCommand = new RelayCommand(this.OnMoveFirstPage);
this.LastPageCommand = new RelayCommand(this.OnMoveLastPage);
}

public RelayCommand NextPageCommand { get; private set; }
public RelayCommand PreviousPageCommand { get; private set; }
public RelayCommand FirstPageCommand { get; private set; }
public RelayCommand LastPageCommand { get; private set; }

public ICollectionView Customers
{
get { return this.pagerSorter.View; }
}

private void OnNextPage()
{
this.pagerSorter.View.MoveToNextPage();
}

private void OnPreviousPage()
{
this.pagerSorter.View.MoveToPreviousPage();
}

private void OnMoveFirstPage()
{
this.pagerSorter.View.MoveToFirstPage();
}

private void OnMoveLastPage()
{
this.pagerSorter.View.MoveToLastPage();
}
}

As you can see,  nothing really new if you do MVVM programming and, like me, use MVVM Light as MVVM toolkit, the only new is that paging and sorting is totally encapsulated inside reusable PagerSorter helper class.
Here’s Page’s XAML:

<navigation:Page x:Class="PagingRia.TestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="1062" d:DesignHeight="484" Title="TestPage Page"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:PagingRia="clr-namespace:PagingRia">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="248" />
<RowDefinition Height="236" />
</Grid.RowDefinitions>
<Grid.DataContext>
<PagingRia:CustomersViewModel />
</Grid.DataContext>
<sdk:DataGrid AutoGenerateColumns="True" ItemsSource="{Binding Customers}" Name="dataGrid1" />
<StackPanel Orientation="Horizontal" Height="20" Grid.Row="1" VerticalAlignment="Top">
<HyperlinkButton Content="First" Command="{Binding FirstPageCommand}" />
<HyperlinkButton Content="Previous" Command="{Binding PreviousPageCommand}" />
<HyperlinkButton Content="Next" Command="{Binding NextPageCommand}" />
<HyperlinkButton Content="Last" Command="{Binding LastPageCommand}" />
</StackPanel>
</Grid>
</navigation:Page>

And here’s final result with full paging and sorting support:
image

Just a final hint: in order to have this solution working it is mandatory to provide data from domainserver in ordered form, this simply means that server side code must have use OrderBy to return related IQueryable<T>

public IQueryable<Customers> GetCustomers()
{
return this.ObjectContext.Customers.OrderBy(cust => cust.ContactName);
}

if you omit this you’ll get a : The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip' exception.

Enjoy paging and sorting! Smile