Search code examples
c#odataodata-v4simple.odata

Problems when querying linked entities with properties that are nullable datetimetimeoffset


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?


Solution

  • 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.