3

I am trying to figure out how to correctly support returning the number of items in a (filtered) data set in my OData API.

My understanding is that adding the $count=true argument to the query string should allow for this.

Now, based on the example from the tutorial in the official docs, adding that parameter should cause the web API to return just an integer number:

The request below returns the total number of people in the collection.

GET serviceRoot/People?$count=true

Response Payload

20

On the other hand, this accepted and quite upvoted1 answer indicates that a query with $count=true will actually return an object, one of whose properties holds said integer number. It provides an exemplary query on a sample endpoint:

https://services.odata.org/V4/Northwind/Northwind.svc/Customers?$count=true&$top=0&$filter=Country eq 'Germany'

Indeed, the actual result from that endpoint is the complex object

{
  "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Customers",
  "@odata.count": 11,
  "value": []
}

instead of the expected result of a single integer number

11

Why is this? Am I misunderstanding the documentation?

1: The answer had 25 upvotes at the time of writing.

F-H
  • 663
  • 1
  • 10
  • 21

1 Answers1

2

The main issue is that the OData v4 specification is an evolving standard, as such many implementations handle some requests differently, either because the standard has changed or because the standard was hard to implement or the suggested behavior in the spec does not conform to the rest of the conventions.

Why is this? Am I misunderstanding the documentation?

So your main issue is that you were reading the wrong documentation for the API that you were querying. It is important to recognize that with each implementation of a standard it is up to the developer to choose how conformant to that standard they are, so you need to read the documentation that goes specifically with that API.

This is the specification in question for OData v4:

4.8 Addressing the Count of a Collection
To address the raw value of the number of items in a collection, clients append /$count to the resource path of the URL identifying the entity set or collection.

The /$count path suffix identifies the integer count of records in the collection and SHOULD NOT be combined with the system query options $top, $skip, $orderby, $expand, and $format. The count MUST NOT be affected by $top, $skip, $orderby, or $expand. The count is calculated after applying any /$filter path segments, or $filter or $search system query options to the collection.

In the .Net implementation because $count is a result of a query, it needs to be evaluated as part of the query options pipeline, not as part of the path.

MS OData QueryOptions - Count
The $count system query option allows clients to request a count of the matching resources included with the resources in the response. The $count query option has a Boolean value of true or false.

Examples:

  • Return, along with the results, the total number of products in the collection http://host/service/Products?$count=true
  • The count of related entities can be requested by specifying the $count query option within the $expand clause. http://host/service/Categories?$expand=Products($count=true)

From an implementaion point of view mixing this query option into the path breaks the convention used for all other processing and url parsing, it really is the odd one out. path and query.

Regarding the object response

In the .Net implementation, because $count is supported in collection expansions as well as on the root (see the second example) they have chosen to inject the value as metadata/attributes mixed in with the results. In that way the response will still be valid for serialization purposes and the count behaviour is again consistent where ever it is used.

This last example I leave you with from one of my own APIs, demonstrating the attribute response for expanded collections, if $count=true didn't return the object graph, I would not be able to get to the counts of the expansions at all:

https://localhost/OData/Residents?$count=true&$expand=Entity($select=Id;$expand=Contacts($count=true;$top=0))&$select=id&$top=2

{
    "@odata.context": "https://localhost/odata/$metadata#Residents(Id,Entity(Id,Contacts()))",
    "@odata.count": 29,
    "value": [
        {
            "Id": 13110,
            "Entity": {
                "Id": 13110,
                "Contacts@odata.count": 6,
                "Contacts": []
            }
        },
        {
            "Id": 13164,
            "Entity": {
                "Id": 13164,
                "Contacts@odata.count": 6,
                "Contacts": []
            }
        }
    ],
    "@odata.nextLink": "localhost/OData/Residents?$expand=Entity%28%24select%3DId%3B%24expand%3DContacts%28%24count%3Dtrue%3B%24top%3D0%29%29&$select=id&$top=2&$skip=2"
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81