2

I'm using simple.odata.client v4 to access my odata rest api. The data model is quite complex. The problem I'm having is that I want to only get entities where related entities fulfilling a condition involving a property that is nullable datetimeoffset (OnHandLastUpdated). Actually on the api side it's a nullable datetime but I think odata v4 converts this automatically. The code I try to run is:

        var items =
oDataClient.For<ClientProductSku>()
    .Filter(x => x.ClientId == clientId && x.Product.SupplierProductSkuClient
        .All(y => y.SupplierProductSku.SupplierProductSkuOnHand
            .Any(z => z.OnHandLastUpdated.HasValue && z.OnHandLastUpdated.Value > DateTimeOffset.Now.AddMinutes(-5))))
    .Expand(UpdateSkuOnhandExpandTables)
    .FindEntriesAsync(annotations)
    .Result;

The error I'm getting is the following:

  "error":{
    "code":"","message":"The query specified in the URI is not valid. Can only bind segments that are Navigation, Structural, Complex, or Collections. We found a segment 'OnHandLastUpdated' that isn't any of those. Please revise the query.","innererror":{
      "message":"Can only bind segments that are Navigation, Structural, Complex, or Collections. We found a segment 'OnHandLastUpdated' that isn't any of those. Please revise the query.","type":"Microsoft.OData.Core.ODataException","stacktrace":"   at Microsoft.OData.Core.UriParser.Parsers.InnerPathTokenBinder.BindInnerPathSegment(InnerPathToken segmentToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindInnerPathSegment(InnerPathToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.EndPathBinder.DetermineParentNode(EndPathToken segmentToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.EndPathBinder.BindEndPath(EndPathToken endPathToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindEndPath(EndPathToken endPathToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.BinaryOperatorBinder.GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.LambdaBinder.BindExpressionToken(QueryToken queryToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.LambdaBinder.BindLambdaToken(LambdaToken lambdaToken, BindingState state)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindAnyAll(LambdaToken lambdaToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.LambdaBinder.BindExpressionToken(QueryToken queryToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.LambdaBinder.BindLambdaToken(LambdaToken lambdaToken, BindingState state)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindAnyAll(LambdaToken lambdaToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.BinaryOperatorBinder.GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.OData.Core.UriParser.Parsers.FilterBinder.BindFilter(QueryToken filter)\r\n   at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource)\r\n   at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseFilter()\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)"
    }

I read somewhere that nullable datetime and datetimeoffset is not yet supported fully in odata v4 which might cause this problem. Is there anyway do a workaround to achieve the above query?

tdbeckett
  • 618
  • 8
  • 19
jopa
  • 145
  • 1
  • 9

2 Answers2

2

I believe this is a current limitation of Simple.OData.Client. It has a built-in expression parser that works similar to other LINQ providers. It converts C# expression to OData URIs. What happens is that it treats HasValue as a property of OnHandLastUpdated, i.e. OnHandLastUpdated is interpreted as a ComplexType, therefore you are getting this error.

I will register an issue at Simple.OData.Client GitHub and see if this can be easily fixed.

I checked the library, and it looks that you can rewrite your query like this:

     var items =
oDataClient.For<ClientProductSku>()
    .Filter(x => x.ClientId == clientId && x.Product.SupplierProductSkuClient
        .All(y => y.SupplierProductSku.SupplierProductSkuOnHand
            .Any(z => z.OnHandLastUpdated.Value > DateTimeOffset.Now.AddMinutes(-5))))
    .Expand(UpdateSkuOnhandExpandTables)
    .FindEntriesAsync(annotations)
    .Result;

Can you try that? I believe it should work.

Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100
  • Thanks for the answer Vagif! FYI I could get around this by adding a OData function on the server side that does this for me instead. – jopa Apr 22 '15 at 11:23
  • That's probably the best strategy anyway when your queries begin to look complicated. And in the meantime I will look into the issue. – Vagif Abilov Apr 22 '15 at 11:44
  • Please check my answer update. I believe it should work without using HasValue/Value properties. – Vagif Abilov Apr 22 '15 at 13:17
0

FWIW, I was manually creating expression trees and came across the same problem. I decided to poke around and try things, and piggybacking on @Vagif Abilov's answer narrowed down the problem to the explicit use of the HasValue property.

If you compare against null instead of Nullable<DateTimeOffset>.HasValue it works, and you don't have to fudge any tests which rely on nullable tests or make a special case for the OData framework.

Therefore following the example above you should be able to do:

 var items = oDataClient.For<ClientProductSku>()
    .Filter(x => x.ClientId == clientId && x.Product.SupplierProductSkuClient
    .All(y => y.SupplierProductSku.SupplierProductSkuOnHand
        .Any(z => z.OnHandLastUpdated != null && 
            z.OnHandLastUpdated.Value > DateTimeOffset.Now.AddMinutes(-5))))
    .Expand(UpdateSkuOnhandExpandTables)
    .FindEntriesAsync(annotations)
    .Result;
lc.
  • 113,939
  • 20
  • 158
  • 187