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("
http://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("
http://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("
http://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: (http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/threads/ )

I look forward to hearing your feedback.

 

Shayne Burgess

Program Manager, Data Services