Counting Entities in an OData Service

We love counting things, a census, and statistics are very popular. Of course, entities in a data service that support the Open Data Protocol (OData) are no different. For example, having a count of the total numbers of entities that you are returning to customers is useful when calculating the total number of pages that can be returned when employing client-side paging. In this blog post, I will endeavor to describe the rationale behind and the means by which you can count entities in the data service.

Service-Side versus Client-Side Counting

One way to count entities in an entity set (OData feed) or that are returned by some specific query is to request the entities from the data service, and count the number of entry elements in the returned response, or the number of materialized entities in the returned IEnumerable<TEntity> collection. But this may not be the best method, especially if you don’t need the entire set of entity data on the client. Because loading a bunch of entities just to count them is a very expensive way to take a census, OData defines the following two methods that enable the data service to count entities for you:

$count Endpoint

OData defines a special $count endpoint. This endpoint, when applied to an entity set (feed) in the URI returns a single value that is a count of all entities in the set, without any XML or JSON decoration; no entity data is returned. The following query returns a value of 91:

https://services.odata.org/Northwind/Northwind.svc/Customers/$count 

The $count endpoint also works with filtered queries, for example, this returns a value of 6, which is the number of Northwind customers in London:

https://services.odata.org/Northwind/Northwind.svc/Customers/$count/?$filter=substringof('London', City)

For more info on the $count endpoint, see Section 3.0 Resource Path in the OData specification.

$inlinecount Query Option

OData also defines an $inlinecount system query option. This query option returns the same value as does $count, but it does so in the <count> element within the response feed, along with the entity data itself. You would use this method to get a total count when paging and you wanted the first page of entities. This URI returns the same count (6) as the previous URI, but it does so along with the six Customer entities:

https://services.odata.org/Northwind/Northwind.svc/Customers/?$filter=substringof('London', City)&$inlinecount=allpages

For more information, see Section 4.9 Inlinecount System Query Option in the OData specification.

(A limitation in both of these methods is that counts are always applied against the resource path, which is limited to a specific entity or collection, which means that you cannot request a count of, say, both a set of selected customers and all their related orders in a single query. You would instead have to either iterate through returned customers and issue requests for a count of each set of related orders, or use $expand to include related orders in the response and count them on the client.)

Counting Support in OData Clients

The main Microsoft-created OData clients (WCF Data Services client, WCF Data Services client for Silverlight, and OData client for Windows Phone) all enable you to request these counts and access the results in a client application. The topic Querying the Data Service covers how to use both the IncludeTotalCount and Count/LongCount methods of DataServiceQuery<T> to get these counts when synchronously accessing the data service. However, things get a little trickier when accessing the data service asynchronously.

Asynchronous Counts

When accessing the data service asynchronously, which is required by both Silverlight and Windows Phone applications, counting gets a little more tricky. This is because there are no BeginCount/EndCount methods. This means that there is no way to asynchronously access the $count endpoint to get only the count without the entity data, as you can by calling DataServiceQuery<T>.Count(). Fortunately, you can get as close as possible to this behavior by using IncludeTotalCount while requesting that zero entities by returned. In this example, we request the inline count, but we also use the Take() method (which is converted to $top in the URI) to ensure that no entity data is returned, just the count element in the response message:

      {
       …
          context =
               new NorthwindEntities(new Uri("https://services.odata.org/Northwind/Northwind.svc/"));
           var  query = (from cust in context.Customers.IncludeTotalCount()
                       where cust.City == "London"
                       select cust).Take(0) as DataServiceQuery<Customer>;           
           query.BeginExecute(OnQueryCompleted, query);
       }

       public void OnQueryCompleted(IAsyncResult result)
       {
           var query = result.AsyncState as DataServiceQuery<Customer>;
           var response = query.EndExecute(result) as QueryOperationResponse<Customer>;
           int count = (int)response.TotalCount;
       }

If you are interested, I used the $inlinecount option with client-side paging when composing the query URI for the OData Windows Phone quickstart.

Cheers!

Glenn Gailey