(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Paging WCF Ria Services entities in Model-View-ViewModel applications

(2 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   66
Comments:   9
More Articles
11 comments   /   posted on Oct 20, 2010
This article is compatible with the latest version of Silverlight.

Download sources & video

There is no doubt that "WCF Ria Services" are a great tool for implementing applications that need to present data from any source. There it is a lot of work spared in creating services and proxies and interacting with the service itself. Personally the thing I love so much is the Validation tools that are very powerful and complete and the capability of sharing code between server and client that make the separation between them something of undistinguishable.

Toghether with WCF Ria Services there are a set of components that are not so useful to consume services. Particularly I usually prefer not to use components like DomainDataSource because it brings my queries directly into the Views and it is a very bad thing. Microsoft has spent long time to create similar components - I remember SqlDataSource and LinqDataSource in ASP.NET - but they are targeted for very simple applications that have a short lifecycle. Someone found ways to use the DomainDataSource in MVVM scenarios but my feel is again bad because of its intrinsec slowness and because many thing I have to do are not strongly typed and this opens the way to runtime errors I wouldn't want to deal with.

In this article I would want to investigate a possible solution, made with a custom PagedDataSource component specifically created for MVVM applications, and the way it might be useful to page queries.

Discovering the inside of DataSource controls

If you investigate about the intimate functioning of the DomainDataSource and of some other similar components (e.g. the CollectionViewSource) you will soon meet an interface ICollectionView that is the very inner core of the component. This interface it the source of many of the functions you expect from the DataSource components while it support current record, filtering and sorting. Watching in more detail you will find also another interesting set of interfaces including IEditableCollectionView and IPagedCollectionView. The one I would like to delineate here is the IPagedCollectionView that grants the capability of paging resultsets. Here is the definition of this interface:

 public interface IPagedCollectionView
 {
     // Events
     event EventHandler<EventArgs> PageChanged;
     event EventHandler<PageChangingEventArgs> PageChanging;
  
     // Methods
     bool MoveToFirstPage();
     bool MoveToLastPage();
     bool MoveToNextPage();
     bool MoveToPage(int pageIndex);
     bool MoveToPreviousPage();
  
     // Properties
     bool CanChangePage { get; }
     bool IsPageChanging { get; }
     int ItemCount { get; }
     int PageIndex { get; }
     int PageSize { get; set; }
     int TotalItemCount { get; }
 }
   

The interface contains a big number of members, but we can shorten them to a bunch of groups; There are a set of methods to perform navigation in the result set, moving the cursor up and down to the pages. We have a property (PageSize) to configure the component and other properties to read the current state, including the current page index, and the number of items in the view. Finally we have a couple of events that notify about the start and end of the page changes.

The beautiful of this interface is that controls like DataGrid and DataPager directly understands and interacts with it to perform data pagination. So implementing the inteface is the first step to have a MVVM-targeted PagedDataSource. Then we will can pass the component directly to Data controls and having them work seamless like a normal DataSource control.

Implementing the PagedDataSource

Before starting to develop the new DataSource it is important to understand what is the expected behavior. First of all, starting from what we do not want, the component will not be used in the XAML. This mean it is not required it implement a base class like Control or ContentControl. As we will see in the following of the article the component will be very lightweight and it will suffice to implement only a little set of interfaces.

The preferred usage of our new PagedDataSource is that it is exposed by a property in the ViewModel, and this property binded to the controls that have to show the data. To achieve this result it is important that the DataSource implements IEnumerable and INotifyCollectionChanged. This way we can directly connect the consumer to the data source and having it populated and also having the consumer react to the changes we will make to the data.

Finally, due the fact that some properties exposed by the component have to be monitored by the consumer, we have to implement also the INotifyPropertyChanged interface. So, here is how it appear the declaration of the PagedDataSource class:

 public class PagedDataSource<T> : IPagedCollectionView, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged
     where T : Entity
 {
     // implement class here
 }

Watching at these few lines there is another thing to say: The component is declared as a Generic type, with a constraint that impose T to be derived from the class Entity. You can understand this constraint if you remember that every WCF Ria Services method returns a IQueryable<T>, where T will be an Entity of the underlying datamodel. What we will expect is to pass a query to the PagedDataSource and having the query "automagically" paged. So, T will be an element we will use often during the implementation to shorten the code required to use the PagedDataSource.

The following step is to connect the PagedDataSource to the DomainContext. It is not required a specific type of domain context but simply handle every class deriving from DomainContext so we will can handle every type of data source and not only the Entity Framework. In the constructor we take a reference to the EntitySet<T> that has to be contained in the DomainContext, for future references.

 public PagedDataSource(DomainContext context)
 {
     this.PagedEntitySet = context.EntityContainer.GetEntitySet<T>();
     this.Context = context;
 }

Once we have initialized it the PagedDataSource it start to follow a precise lifecycle. First of all it loads the first page, according with the specified PageSize and PageIndex, then it notifies the consumer of the data available raising a CollectionChanged event. The consumer now reads the entity instances from the PagedDataSource using the implemetation of IEnumerable<T>. When the ViewModel or a DataPager calls one of the Move methods, the component loads the new page and again notifies the consumer. The lifecycle is really simple but it have its core in the Refresh method that is responsible to load the required page of entities, without loading all the data in the table. Here is the source of the method:

 public void Refresh()
 {
     this.IsPageChanging = true;
  
     if (this.OnPageChanging(this.PageIndex))
    {
         this.IsPageChanging = false;
         return;
     }
   
     this.PagedEntitySet.Clear();
  
     QueringEventArgs<T> query = new QueringEventArgs<T>();
     this.OnQuering(query);
   
     query.Query.IncludeTotalCount = true;
   
     this.Context.Load<T>(
         query.Query
             .Skip(this.PageIndex * this.PageSize)
             .Take(this.PageSize),
         LoadBehavior.RefreshCurrent,
         lo =>
         {
             this.IsPageChanging = false;
             this.ItemCount = this.TotalItemCount = lo.TotalEntityCount;
             this.OnPageChanged();
             this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
         }, null);
 }

Probably it is the most complex method in the component so let me explain it row-by-row; In the first part the method sets the IsPageChanging property (it is part of the IPagedCollectionView interface) to indicate that the change of the page is in progress. Then it raises the PageChanging event giving to the ViewModel the last chance of cancel the change since its EventArgs is cancelable.

If the code authorizes the component to perform the page change, the EntitySet into the DomainContext is cleared then the component asks the query to perform with a Quering event. Here it expects that the returned arguments contain an EntityQuery<T> that are the data to which we must apply paging rules. There is a specific reason to ask for the query every time we have to refresh the page. This way it lets the developer to change the parameters to the query. It is useful if we need to filter data on the server and have some parameters acquired by the user interface. We will see an use of this in the following parts of the article.

Finally the method append to the query a call to Skip and Take. This is the part that does the trick. Thanks to the capability of serializing queries the call to these methods is passed to the server that apply them to the data just before to send it to the client. If you use Entity Framework the serialized query is applied directly to the generated SQL statement created by EF to extract data. It is really wonderful and astounding.

Under this trick there is WCF Data Services that is the REST implementation build over WCF. When you call the Load method the framework translates it to a querystring that describes the query. Here is a catch from the browser:

When the call to the service returns there is a bunch of tasks to accomplish: after setting the IsPageChanging to false we need to read the TotalItemCount and copy it to two properties of IPagedCollectionView and finally raise the PageChanged event and the CollectionChanged event to ask the consumer to update itself. The TotalItemCount is the total number of records we expect without the paging applied. This information is required to calculate the exact number of pages available.

Model-View-ViewModel aspects

If you know the MVVM pattern you already understand how this class might be useful. We have to expose from the ViewModel a property to which connect the consumer ItemsControl and the DataPager. In my sample I did a step forward and I thought about the fact often you need to have a lot of pages made in the same way just because your application needs to perform CRUD operations to each one. So I created a ListViewModel<T, K> class that include all the logic needed to create a DataGrid + DataPager page:

 public abstract class ListViewModel<T, K>
     where T : Entity
     where K : DomainContext, new()
 {
     /// <summary>
     /// Gets or sets the context.
    /// </summary>
     /// <value>The context.</value>
     protected K Context { get; set; }
  
     /// <summary>
     /// Gets or sets the items.
     /// </summary>
     /// <value>The items.</value>
     public PagedDataSource<T> Items { get; set; }
  
     /// <summary>
     /// Initializes a new instance of the <see cref="ListViewModel<T, K>"/> class.
     /// </summary>
     public ListViewModel()
     {
         this.Context = new K();
         this.Items = new PagedDataSource<T>(this.Context);
         this.Items.Quering += new EventHandler<QueringEventArgs<T>>(Items_Quering);
         this.Items.ItemCreating += new EventHandler<ItemCreatingEventArgs>(Items_ItemCreating);
         this.Items.MoveToFirstPage();
     }
   
     /// <summary>
     /// Wraps the item.
     /// </summary>
     /// <param name="item">The item.</param>
     /// <returns></returns>
     protected abstract object WrapItem(object item);
     /// <summary>
     /// Gets the query.
     /// </summary>
     /// <returns></returns>
     protected abstract EntityQuery<T> GetQuery();
  
     /// <summary>
     /// Handles the ItemCreating event of the Items control.
    /// </summary>
     /// <param name="sender">The source of the event.</param>
     /// <param name="e">The <see cref="SilverlightPlayground.PagingQueries.Data.ItemCreatingEventArgs"/> instance containing the event data.</param>
     private void Items_ItemCreating(object sender, ItemCreatingEventArgs e)
     {
         e.Item = this.WrapItem(e.Item);
     }
  
     /// <summary>
     /// Handles the Quering event of the Items control.
     /// </summary>
     /// <param name="sender">The source of the event.</param>
     /// <param name="e">The <see cref="SilverlightPlayground.PagingQueries.Data.QueringEventArgs<T>"/> instance containing the event data.</param>
     private void Items_Quering(object sender, QueringEventArgs<T> e)
     {
         e.Query = this.GetQuery();
     }
 }

The ListViewModel require two type arguments: T is the entity to page and K is the DomainContext to use. Inside this ViewModel I have a property of type PagedDataSource<T> initialized with the type arguments. The instance of the DomainContext is created by this class and saved in the Context property. The class handles the Quering event and pass it to an abstract method. The reason is that the query to execute is the only thing a concrete ViewModel must provide to the PagedDataSource.

Curiously there is another event, MVVM related, that we have not already met. It is the ItemCreated evnt that is raised every time an item is prepared by the IEnumerable implementation of the PagedDataSource. This event is useful when you have to bind to a DataGrid and collect from each row some commands (e.g. open, delete, etc...). If you bind to the DataGrid the pure entity there is not any way to handle these events but you need to have a row ViewModel that wraps the entity and exposes the commands. The ItemCreated let you to intercept the item just before it is provided to the DataGrid and let you create the required RowViewModel<T> instance.

Here is the final code from the concrete ViewModel of the datagrid page:

 public class MainPageViewModel : ListViewModel<Item, TestDomainContext>
 {
     /// <summary>
    /// Gets or sets the max value.
     /// </summary>
    /// <value>The max value.</value>
     public int MaxValue { get; set; }
    /// <summary>
     /// Gets or sets the show command.
     /// </summary>
     /// <value>The show command.</value>
     public DelegateCommand<Item> ShowCommand { get; set; }
     /// <summary>
     /// Initializes a new instance of the <see cref="MainPageViewModel"/> class.
     /// </summary>
     public MainPageViewModel()
     {
         this.ShowCommand = new DelegateCommand<Item>(o => this.Show(o));
         this.MaxValue = 10000;
     }
  
     /// <summary>
    /// Gets the query.
     /// </summary>
     /// <returns></returns>
     protected override EntityQuery<Item> GetQuery()
     {
         return this.Context.GetNumbersQuery(1, this.MaxValue);
     }
   
     /// <summary>
     /// Wraps the item.
    /// </summary>
     /// <param name="item">The item.</param>
     /// <returns></returns>
     protected override object WrapItem(object item)
     {
         return new NumberRowViewModel((Item)item, this.ShowCommand);
     }
  
     /// <summary>
    /// Shows the specified item.
     /// </summary>
     /// <param name="item">The item.</param>
     private void Show(Item item)
     {
         // do what you want with the selected item
     }
 }

The code from the attached sample has been cut to the minimal required to apply paging.

Future improvements

As you have seen the PagedDataSource is a key component to simplify the development in MVVM scenarios. It grants the capability to easily apply paging and customize the queries. Obviously there are many improvements you can imagine. Since it does not require a Filter event like the CollectionViewSource does, because you apply restrictions directly to the query, it is understandable someone needs to add sorting to the PagedDataSource. I figure out you can do this task in multiple ways, but please remember the better is add another part of the query into the Refresh method and having the sorting execute on the server side on all the records in the repository and not only on the few rows returned.


Subscribe

Comments

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by HC on Oct 25, 2010 14:29

    Hello,

    You have some bugs in the class PagedDataSource. That bugs made possible to go after the last page in the DataPager.

     Changes need to fix that behavior.

    public bool MoveToLastPage()
            {
                this.PageIndex = Math.Max(1, (int)Math.Ceiling((double)(((double)this.ItemCount) / ((double)this.PageSize)))) - 1;
                this.Refresh();
                return true;
            }
     public bool MoveToNextPage()
            {
                int maxPage = Math.Max(1, (int)Math.Ceiling((double)(((double)this.ItemCount) / ((double)this.PageSize))));
                if (pageIndex < (maxPage -1))
                {
                    this.PageIndex++;
                    this.Refresh();
                    return true;
                }

                return false;
            }
    public bool MoveToPage(int pageIndex)
            {
                int maxPage = Math.Max(1, (int)Math.Ceiling((double)(((double)this.ItemCount) / ((double)this.PageSize))));
                if (pageIndex >= 0 && pageIndex < maxPage)
                {
                    this.PageIndex = pageIndex;
                    this.Refresh();
                    return true;
                }

                return false;
            }

    In the code of the view you have the dataPager, if you register the PageIndexChangedEvent to the following method you disable the next button when in the last page.
     void pager_PageIndexChanged(object sender, EventArgs e)
            {
                var pager = (DataPager)sender;

                int pageCount = Math.Max(1, (int)Math.Ceiling((double)(((double)pager.ItemCount) / ((double)pager.PageSize))));
                if (pager.PageIndex == pageCount - 1)
                {
                    pager.IsTotalItemCountFixed = true;
                }
                else pager.IsTotalItemCountFixed = false;

            }


  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Andrea Boschin on Oct 25, 2010 14:50

    Are you referring to the attached project? I didn't observed the described behavior in my example. Are you sure your ria service is returning the correct TotalEntitiesCount?

    bye.

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by HC on Oct 25, 2010 15:39

    Yes, i'm referring to the attached project.

    When i run the project if i click last page in the DataPager and after that i click the next page the datapager goes to the next page.

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Long Xue on Nov 05, 2010 18:44

    Excellent article, Andrea.

    The behavior HC mentioned happened to me as well, HC's solution fixed it.

    Best regards,

    Long Xue

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Eyad Al-Hodiani on Nov 14, 2010 11:04

    This is a great article, but i have some issues about performance , how can i get data from webservice just one time.

    In your example in every change  in page index , we need to request the data from webservice.

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Andrea Boschin on Nov 14, 2010 11:08
    cachig is not implemented. you can do it by yourself in the method that retrieve the data.
  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Marc Ederwind on Dec 14, 2010 16:04

    Nice work but it seams that your PagedDataSource does not notify the interface when an entity is added or removed from the inner EntitySet....

  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Aleksey on Jan 27, 2011 05:48
    Thanks 4 article!
  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by Christian Schmidtchen on Feb 08, 2011 17:25
    Thank you, very great article! I have only one question: How I notice that the loading is complete? I have some methods that should start when the data are loades.
  • -_-

    RE: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by ielektronick on Feb 14, 2011 18:20
    Thanks! That post is very-very helpful!
  • mateofd

    Re: Paging WCF Ria Services entities in Model-View-ViewModel applications


    posted by mateofd on Dec 22, 2011 21:49

    This helped me a lot!!

    I had to apply the fixes suggested by HC and I also had to override the Count() method in my DomainService class as mentioned in:

     http://silverlightfeeds.com/post/2900/WCF_RIA_Services_and_TotalEntitiesCount-1.aspx
    

Add Comment

Login to comment:
  *      *