Jaa


Server Paging in Data Services

The Data Services team recently released an update to.NET 3.5 SP1 that adds a number of features to data services – we are calling this update Data Services V2. One of the major features included in this update is the ability to enable server paging on a Data Service. In this post, I’ll introduce you to the concept of page limits, I will teach you how to set limits on a service, and finally I will teach you how to work with the partial pages in the data services client.

If you already know what server paging is and just want to enable it, you can skip to part 2. If you don’t care about setting limits on the server and just want to know how to create clients that can work with a service that has paging limits, you can skip to Part 3.

Part 1: What are Page Limits?

As a concept, page limits are fairly simple, they allow the service author to limit the size of a response to a given query. When the service receives a query that requires it to exceed those limits, it issues a partial response that includes only as much of the results as it could without exceeding the limit and includes in the response a “link” that tells the client how to get the rest of the results.

Page limits are a key concept if you are exposing a data service to the world and you want to ensure that clients can’t overload your service by issuing queries that require an excessive amount of resources to respond to. The paging limits also work as a built-in load balancing tool by distributing large queries over a number of smaller partial queries which will reduce the impact of large queries on the overall response times of the system. The paging has one final benefit in that it forces the developers of client apps to think about the size of the queries they are issuing against the service and smart developers can lazy-load only the pages they need in an on-demand fashion.

Part 2: Enabling Paging Limits on a Service

By this point, you should be saying to yourself: “Great, I’m sold, page limits are the greatest things since sliced bread, now how do I enable them already?”. In the rest of this section I will tell you exactly that.

Paging limits on a service can be set by specifying per collection limits on the total number of entities returned for each request. This can be done through the DataServiceConfiguration API during service initialization. For this walk-through I am going to assume you are already familiar with data service creation and that you have a service setup that is exposing an EF model of a Northwind database.

The following code shows setting the page limits for the Customers, Order and Order_Details entity sets. For the sake of the example, I am setting the paging limits very small.

public static void InitializeService(DataServiceConfiguration config) { // TODO: set rules to indicate which entity sets and //service operations are visible, updatable, etc. // Examples: config.SetEntitySetAccessRule("*", EntitySetRights.All); config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);

    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;

    config.SetEntitySetPageSize("Customers", 1); config.SetEntitySetPageSize("Orders", 1); config.SetEntitySetPageSize("Order_Details", 2); }

Key Concept: One important thing to note in the sample above is that enabling page sizes requires the service to use a higher OData Protocol version in the response to requests that exceed those limits. Because of this, I have set the MaxProtocolVersion of the service in the above example to V2 – this tells the server it is O.K. to respond with responses that use the V2 OData Protocol. It is also important to note that clients created without using the update released recently will not know how to work with paged responses; you should carefully consider the impact on any existing V1 clients you may have before enabling these limits.

Now that we have enabled some page limits on the service lets look at the affect this has on the responses to queries to those entity sets. The image below shows the OData response to the query “/Customers” in the browser.

image

If you are familiar with the OData Protocol, you will quickly see that there is only one customer in this feed. I have also highlighted a link at the bottom of the response – this is the “next” link that the service includes in this partial response that tells the client how to get the next partial set and continue making progress on the initial query.

It is also instructional to examine the response to a request that includes more than one entity set. The image below shows the end of response to the query “Customers?$expand=Orders”.

image

In the above picture, I have highlighted two “next” links – the bottom next link is the next link for the outer Customers set. The top link is the link for making progress on orders collection of the customer ‘ALFKI’. Because I have also set a limit on the orders collection, the service has also paged the orders collection that belongs to each Customer. In general, if you have nested sets within a response and they have paging limits there will be a next link for each set in the response.

Part 3: Working with Paging Limits on the Client

So if you have read this post from the start, you understand server based paging and you know how to configure it on the server so the next question is “how do you work with the paged results on the client?”.

Once again, I am going to assume you are familiar with the Data Services client and you have a data service setup that exposes the Northwind database and you have defined page limits on the Customers and Orders entity sets.

First create a client app (a simple command line app will do). Next, you will want to create a service reference in your client app. We will be making heavy use of the DataServiceCollection in these examples so you will want to make sure your service reference generates the DataServiceCollection.

Note: By default, the client side types generated by ADO.NET Data Services in Visual Studio 2008 do not user the new DataServiceCollection for backwards compatibility reasons. To get client side types that use the DataServiceCollection, you have to tell the code generation library that you want client code with this collection. To do this, set an environment variable before starting Visual Studio and then use Add Service Reference (ASR) to generate client proxy code. The environment variable to set is:

set dscodegen_usedsc=1

Now that we have a client application setup lets look at an example of querying:

static void Main(string[] args) { //create the client context NorthwindEntities nwsvc = new NorthwindEntities( new Uri("https://localhost:56304/nw.svc/"));

    //create a DSC of customers and load from the service DataServiceCollection<Customers> custs = new DataServiceCollection<Customers>( nwsvc.Customers );

    //print the count of customers retrieved Console.WriteLine(custs.Count); Console.Read(); }

If you run the above snippet, you will find that the count value printed out is 10 but if you look at the Northwind database you will find 91 Customers in the database. What happened? I set the page limit at 10 on the service so that when the Customers entity set is queried only 10 results are returned – not 91.

image

So the next question is: how do I get the rest of the Customers? The following snippet shows querying for all customers by using the continuation token:

static void Main(string[] args) { //create the client context NorthwindEntities nwsvc = new NorthwindEntities( new Uri("https://localhost:56304/nw.svc/"));

    //create a DSC of customers and load from the service DataServiceCollection<Customers> custs = new DataServiceCollection<Customers>( nwsvc.Customers );

    //check for the existence of a continuation token while (custs.Continuation != null) { //use the token to query for more customers //and load the results back into the collection custs.Load( nwsvc.Execute<Customers>(custs.Continuation) ); //print the current count of customers retrieved Console.WriteLine(custs.Count); }

    //print the count of customers retrieved Console.WriteLine(custs.Count); Console.Read(); }

When I execute this program, the results window shows the client executing multiple requests before loading the entire Customers entity set into the collection.

image

Key Concept: The sample code I have shown above can be improved by not always blindly loading the entire set into the client but by only loading the subsequent pages on demand. A much better pattern is to Load the first page of the partial set into the client collection, work with it, and only load the next page if needed. This will help reduce both the volume of network traffic created by your client application but also the load on the service.

It is not necessary to always use the DataServiceCollection when working with paged results on the client. The QueryOperationResponse object returned from DataServiceQueries also contains the Continuation token needed to make progress on a partial set. The following examples show the sample actions as in the previous example but this time without the DataServiceCollection.

static void Main(string[] args) { //create the client context NorthwindEntities nwsvc = new NorthwindEntities( new Uri("https://localhost:56304/nw.svc/"));

    //create a query to get the customers var query = nwsvc.Customers.Execute();

    //load the customers into a list List<Customers> custList = query.ToList();

    //check for the continuation token while (((QueryOperationResponse)query).GetContinuation() != null) { //query for the next partial set of customers query = nwsvc.Execute<Customers>( ((QueryOperationResponse)query).GetContinuation().NextLinkUri );

        //Add the next set of customers to the full list custList.AddRange(query.ToList());

        //print the current count of customers retrieved Console.WriteLine(custList.Count); }

    //print the count of customers retrieved Console.WriteLine(custList.Count); Console.Read(); }

Providing Feedback

The forum intended for questions on all current versions of WCF Data Services is available at: (https://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/threads/ )

I look forward to hearing your feedback.

 

Shayne Burgess

Program Manager, Data Services

Comments

  • Anonymous
    February 03, 2010
    So how would you specify page size from the client side? For example, in the typical grid paging scenario where the user can select the number of results to show per page (show 15/30/all etc). It would be much more useful if you could do something like Customers?$skip=30&take=15.
  • Anonymous
    February 03, 2010
    Sorry, some basics first - what is that Data Paging and why one would need it?
  • Anonymous
    February 07, 2010
    @Angie - Client-side paging was included in the initial release of Data Services in .NET 3.5 SP1. The syntax is almost exactly what you suggest: Customers?$skip=30&$top=15. This feature has not gone away, though with server-driven paging, you may get fewer entities than you asked for.@Arthur - The point of paging is to limit the amount of data being downloaded from the server. Client-driven paging typically has to do with how much information you want to display to the user at a time, whereas server-driven paging is intended to limit the load a client can put on the server. Does that help at all?
  • Anonymous
    February 08, 2010
    How to use the Server-paging with Data Source controls (like ObjectDataSource control on an aspx page)?
  • Anonymous
    February 08, 2010
    @Arthur,Paging, essentially involves breaking a collection into more manageable 'chunks' or pages.You do mainly to avoid requesting data you can't currently display because of limited screen real estate.Some clients do this for themselves using $top & $skip, i.e. they are good citizens, they only ask for what they need.Sometimes however users / applications inadvertently ask for data without realizing satisfying the request would result in potentially millions of rows of data...this is where server driven paging comes in, it allows a 'compliant' OData service to throttle data to the client, returning on a page at a time.Naive clients will be satified, but if the client really did want all the data, they can get more using the appropriate $skiptokens etc.Anyway hope this helpsAlex
  • Anonymous
    March 04, 2010
    You can find more about server-driven paging on MSDN @ http://msdn.microsoft.com/en-us/library/ee473424.aspx.More general information about configuring a WCF Data Service is also available on MSDN @ http://msdn.microsoft.com/en-us/library/ee358710.aspx.
  • Anonymous
    July 18, 2010
    I presume this is a forward-only approach to data navigation?  For ex., you have a token to get the 'next' data, but you cannot get a token to 'previous' .  The reason I bring this up, is b/c I haven't been able to fit the Silverlight 3 DataPager control into use with WCF DS paging, is there a way to do this.Thanks!
  • Anonymous
    July 19, 2010
    Please remove my posts, I posted and there is answer in the MSDN forums.  Sry bout that.