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.
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.
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,
"[email protected]": 6,
"Contacts": []
}
},
{
"Id": 13164,
"Entity": {
"Id": 13164,
"[email protected]": 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"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With