Search code examples
c#asp.net-web-apiodataiqueryable

WebAPI OData $Skip on custom IQueryable double applied


I have implemented a custom IQueryable that is exposed via a WebAPI OData endpoint. The structure of the controller's Get() is rather standard:

[EnableQuery(
    AllowedQueryOptions = AllowedQueryOptions.Count
                          | AllowedQueryOptions.Filter
                          | AllowedQueryOptions.OrderBy
                          | AllowedQueryOptions.Skip
                          | AllowedQueryOptions.Top)]
[ODataRoute]
public PageResult<Foo> Get(ODataQueryOptions<Foo> queryOptions)
{

    var bars = new QueryableData<Foo>(_provider);

    var result = ((IQueryable<Foo>)queryOptions
        .ApplyTo(bars,
            new ODataQuerySettings(new ODataQuerySettings { EnableConstantParameterization = false, EnsureStableOrdering = false }))).ToList();
    var count = _provider.Count;
    return new PageResult<Foo>(result, null, count);
}

The odd behavior I am seeing, is that an OData $Skip in the query string is applied after the PageResult is returned. For example:

  • if the query string contains a ?$top=10&$skip=10 there will be no results return.
  • if the query string contains a ?&top=12&skip=10 there will be (2) results returned.

What I am looking to do is prevent the framework(s) from applying the Skip to my results set since the query provider is already implementing the skip. Are there ODataQuerySettings that can be set to prevent this double application of the skip?

EDIT: Upon further investigation, when I remove $count=true from the query string skip (and top) function as expected. This leads me to believe that my approach to implementing $count=true is incorrect. From my debugging sessions it appears that when $count=true is in the query options the queryable has the expression tree applied to it twice, once with a return type of long, and then again without the wrapping countlong expression. I have tried returning the count on the first pass and then proper queryable for the second pass, but this results in the delayed application of the skip expression. There seems be be something very fundamental that I am missing here.


Solution

  • While reading through the Github issues list I came across this post: OData PageResult method ignoring count parameter when using EnableQuery attribute #159. What appears to be the problem is the combination of EnableQuery Attribute and the parameterized Get constructor taking the ODataQueryOptions. Using both means that you will implement the constructor query options, applying the query expressions, then the framework will apply what filters it can on direction from the applied attribute; therefore double applying things like skip, top and orderby.