Search code examples
c#odata

How to ask for entities having more than 1 nested entity using the generated C# OData client code?


I am playing with the TripPin OData demo from https://github.com/OData/ODataSamples/tree/master/Scenarios/TripPin/src/webapi using the OData C# client code generated by the OData template introduced by the following VS extension: enter image description here

The C# code is simple:

var dc = new DefaultContainer(new Uri("http://canws212:23890/"));
dc.People.AddQueryOption("$expand", "Trips").Where(p => p.Trips.Any()).First();

This returns the first Person entity having any Trip child entities in the Trips property.

But now I wish to get the first Person entity having more than one Trip child:

dc.People.AddQueryOption("$expand", "Trips").Where(p => p.Trips.Count() > 1).First();

This does not work. The request/response as captured by fiddler are:

Request

GET http://canws212:23890/People?$filter=Trips/$count%20gt%201&$top=1&$expand=Trips HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
Host: canws212:23890
Connection: Keep-Alive

Response

HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
OData-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRGF5Zm9yY2VcU291cmNlXFJlcG9zXE9EYXRhU2FtcGxlc1xTY2VuYXJpb3NcVHJpcFBpblxzcmNcd2ViYXBpXE9EYXRhU2FtcGxlcy5XZWJBcGlTZXJ2aWNlXFBlb3BsZQ==?=
X-Powered-By: ASP.NET
Date: Sat, 23 Jul 2016 01:23:41 GMT
Content-Length: 1604

{
  "error":{
    "code":"","message":"The query specified in the URI is not valid. The method or operation is not implemented.","innererror":{
      "message":"The method or operation is not implemented.","type":"System.NotImplementedException","stacktrace":"   at Microsoft.OData.Core.UriParser.Visitors.QueryNodeVisitor`1.Visit(CountNode nodeIn)\r\n   at Microsoft.OData.Core.UriParser.Semantic.CountNode.Accept[T](QueryNodeVisitor`1 visitor)\r\n   at System.Web.OData.Query.ParameterAliasNodeTranslator.Visit(BinaryOperatorNode nodeIn)\r\n   at Microsoft.OData.Core.UriParser.Semantic.BinaryOperatorNode.Accept[T](QueryNodeVisitor`1 visitor)\r\n   at System.Web.OData.Query.FilterQueryOption.get_FilterClause()\r\n   at System.Web.OData.Query.Validators.FilterQueryValidator.Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings)\r\n   at System.Web.OData.Query.FilterQueryOption.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at System.Web.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.OData.EnableQueryAttribute.ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)\r\n   at System.Web.OData.EnableQueryAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)\r\n   at System.Web.OData.EnableQueryAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)"
    }
  }
}

What is wrong? Is it a defect of the sample TripPin application or am I doing something wrong? How do I fix it?

EDIT

I would like the filtration to occur on the database itself. I.e. situation where first all the Person entities are fetched and then filtered out is far from ideal.


Solution

  • When you run the c# code

    dc.People.AddQueryOption("$expand", "Trips").Where(p => p.Trips.Count() > 1).First();
    

    Odata.Client API convert the query to URL.

    The generated url in your case is:

    http://canws212:23890/People?$filter=Trips/$count gt 1&$top=1&$expand=Trips  
    

    which doesn't match the URL Convention of OData V4

    $filter is applied only on the properties of class (People) , and the class People can't navigate to the class Trip using the $filter keyword and there is no property count.

    You expand Trips by the keyword Expand

    to read more about OData Version 4.0. URL Conventions :

    http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html

    so, you get the error message

    Edit2:

    To get the first Person entity having more than one Trip

    (I test it with url: http://services.odata.org/V4/TripPinServiceRW )

     var dc = new DefaultContainer(new Uri("http://canws212:23890"));
     var firstPerson = dc.People.AddQueryOption("$expand", "Trips")
                                .ToList()
                                .FirstOrDefault(p => p.Trips.Count > 1);
     Console.WriteLine("{0} have: {1} trip", firstPerson.FirstName, firstPerson.Trips.Count);
    //The result: Russell have: 3 trip
    

    Edit: 3 if you review example 28, in the url:

    http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html

    Example 28: entity count in a $filter expression.  
    http://host/service/Categories?$filter=Products/$count gt 0
    

    you find that V4 support count property so ,MY Conclusion

    TripPin Demo site doesn't comply with URL Convention V4

    and your query is valid , but can't be executed(for no count support)

      http://canws212:23890/People?$filter=Trips/$count gt 1&$top=1&$expand=Trips