WebAPI OData $Skip on custom IQueryable double applied

不羁岁月 提交于 2019-12-08 17:47:07

问题


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.


回答1:


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.




回答2:


Since I wanted to handle the skip myself but I still wanted the other features of the EnableQueryAttribute I had a look and noticed it was virtual. I created a derived class and then attempted to override the ApplyQuery methods. Unfortunately the ODataQueryOptions only had private sets on its properties so I ninja-ed in a little reflection. (It feels dirty but hey..)

public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
    var skipOption = new SkipQueryOption("0", queryOptions.Context);
    typeof(ODataQueryOptions).GetProperty("Skip").SetValue(queryOptions, skipOption, null);

    return base.ApplyQuery(queryable, queryOptions);
}

With the skip option now being 0 it doesn't apply it when constructing the response and no more "double skip blues".




回答3:


I have found that if you are using swagger/swashbuckle, removing the attribute all together will cause some of the OData options to not be generated by swashbuckle. Creating your own attribute that overrides the ApplyQuery method to do nothing and just return the original query works as expected.

    // OData framework's EnableQuery attribute will apply query's again, after we have already applied the query to the result set
    // (So For e.g. you will get Top and Skip applied again on your results that have already had top and skip applied
    // this is a workaround the disables client side queries until this is fixed.
    // https://github.com/OData/WebApi/issues/159
    public class EnableCustomQueryAttribute : EnableQueryAttribute
    {
        public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
        {
            return queryable;
        }
    }



回答4:


Based on slamb2k's response, a bit more up to date version:

public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
    var parser = typeof(ODataQueryOptions).GetField("_queryOptionParser", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(queryOptions) as ODataQueryOptionParser;
    typeof(ODataQueryOptions).GetProperty("Skip").SetValue(queryOptions, new SkipQueryOption("0", queryOptions.Context, parser), null);
    typeof(ODataQueryOptions).GetProperty("Top").SetValue(queryOptions, new TopQueryOption("0", queryOptions.Context, parser), null);
    typeof(ODataQueryOptions).GetProperty("OrderBy").SetValue(queryOptions, new OrderByQueryOption("0", queryOptions.Context, parser), null);

    return base.ApplyQuery(queryable, queryOptions);
}


来源:https://stackoverflow.com/questions/30608837/webapi-odata-skip-on-custom-iqueryable-double-applied

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!