的OData的WebAPI pre过滤扩展查询 [英] WebAPI OData pre filtering expand queries

查看:581
本文介绍了的OData的WebAPI pre过滤扩展查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有可能以pre-过滤器的OData导致一个的WebAPI的扩大子句中的项目。我只希望基于与已删除国旗的predefined接口上此进行过滤。

 公共接口IDbDeletedDateTime
{
    约会时间? DeletedDateTime {搞定;组; }
}公共静态类IDbDeletedDateTimeExtensions
{
    公共静态的IQueryable< T> FilterDeleted< T>(这IQueryable的< T>个体经营)
        其中T:IDbDeletedDateTime
    {
        返回self.Where(S = GT; s.DeletedDateTime == NULL);
    }
}公共类人:IDbDeletedDateTime
{
     [键]
     公众诠释PERSONID {搞定;设置}
     公众的DateTime? DeletedDateTime {搞定;组; }
     公共虚拟的ICollection<宠物及GT;宠物{搞定;组; }
}公共类宠物:IDbDeletedDateTime
{
     [键]
     公众诠释PetId {搞定;设置}
     公众诠释PERSONID {搞定;设置}
     公众的DateTime? DeletedDateTime {搞定;组; }
}
公共类PersonController:ApiController
{
    私人PersonEntities DB =新PersonEntities();    [EnableQuery]
    // GET:API /人
    公众的IQueryable<&人GT; GetPersons()
    {
        返回db.Persons.FilterDeleted();
    }
}

您可以看到,我很容易过滤删除的人。当有人会从像查询 / API /人?$ =拓展宠物

删除宠物的问题就来了

有没有一种方法来检查,如果这片宠物的是一个IDbDeletedDateTime,并相应地进行筛选?也许有更好的方式来处理这个?

编辑:

我想基于什么在这个答案捡起来解决这个问题。我不认为这是可以做到的,至少不是在所有情况下。一个 ExpandedNavigationSelectItem ,即使看起来是关系到过滤器的唯一部分是 FilterClause 。这可以为空时,它没有任何过滤的,它仅仅是一个getter属性的,这意味着我们如果我们想用一个新的过滤器不能设置。天气与否有可能修改当前过滤器只覆盖了我不是,如果我不能刚添加过滤器特别感兴趣,小的用例。

我有一个扩展方法,将通过所有的扩展条款递归,你至少可以看到什么FilterOption是每个扩展。如果有人能得到这个90%code完全实现,这将是惊人的,但我不会抱着它我的呼吸。

 公共静态无效FilterDeletables(此ODataQueryOptions queryOptions)
{
    //此处定义递归函数。
    //我选择了做它,因为我不想让此功能的实用方法这种方法。打破它由您决定。
    动作< SelectExpandClause> filterDeletablesRecursive = NULL;
    filterDeletablesRecursive =(selectExpandClause)= GT;
    {
        //没有条款?跳跃。
        如果(selectExpandClause == NULL)
        {
            返回;
        }        的foreach(在selectExpandClause.SelectedItems VAR将selectedItem)
        {
            //我们只找展开的导航项目。
            VAR expandItem =(selectedItem设置为ExpandedNavigationSelectItem);
            如果(expandItem!= NULL)
            {
                //https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
                //文档指出:获得此级别扩展路径此路径包括零个或多个类型细分后只有一个导航属性​​。
                //假设文档是正确的,我们可以假设总是会有的,我们可以使用结束时一起NavigationPropertySegment。
                VAR edmType = expandItem.PathToNavigationProperty.OfType< NavigationPropertySegment>()最后()EdmType。;
                字符串stringType = NULL;                IEdmCollectionType edmCollectionType = edmType为IEdmCollectionType;
                如果(edmCollectionType!= NULL)
                {
                    stringType = edmCollectionType.ElementType.Definition.FullTypeName();
                }
                其他
                {
                    IEdmEntityType edmEntityType = edmType为IEdmEntityType;
                    如果(edmEntityType!= NULL)
                    {
                        stringType = edmEntityType.FullTypeName();
                    }
                }                如果(!String.IsNullOrEmpty(stringType))
                {
                    键入actualType = typeof运算(PetStoreEntities).Assembly.GetType(stringType);
                    如果(actualType = NULL&放大器;!&安培; typeof运算(IDbDeletable).IsAssignableFrom(actualType))
                    {
                        VAR过滤= expandItem.FilterOption;
                        //expandItem.FilterOption =新FilterClause(新BinaryOperatorNode(BinaryOperatorKind.Equal,新,));
                    }
                }                filterDeletablesRecursive(expandItem.SelectAndExpand);
            }
        }
    };    filterDeletablesRecursive(queryOptions.SelectExpand .SelectExpandClause?);
}


解决方案

纠正我,如果我理解错了:你想,如果他们实现该接口始终过滤实体 IDbDeletedDateTime ,所以当用户想扩展您还希望筛选如果导航属性实现该接口的导航属性,对吧?

在你目前的code你启用OData的查询选项,用 [EnableQuery] 属性,这样的OData将处理扩展查询选项,供您和宠物不会被过滤你想要的方式。

您必须实现自己的 [MyEnableQuery] 属性的选项,并覆盖 ApplyQuery 方法:检查有如果用户设置的 $展开的查询选项,如果是这样,检查请求的实体工具 IDbDeletedDateTime 并相应地进行筛选。

您可以检查<一href=\"https://github.com/OData/WebApi/blob/master/OData/src/System.Web.OData/OData/EnableQueryAttribute.cs\"相对=nofollow>这里的 [EnableQuery] 属性的code和看到,在 ApplyQuery 方法,你可以访问对象 ODataQueryOptions 将包含所有用户设定的查询选项(的WebAPI填充来自URI查询字符串此对象)。

这将是一个通用的解决方案,你可以在你的控制器的所有方法,如果你打算有几个实体,与您的自定义过滤功能,接口使用。如果你只想要这对于单个控制器的方法,还可以删除 [EnableQuery] 属性,并直接在控制器方法调用查询选项:添加 ODataQueryOptions 参数,你的方法和手动处理的查询选项。

这会是这样的:

  // GET:API /人
公众的IQueryable&LT;&人GT; GetPersons(ODataQueryOptions queryOptions)
{
    //检查queryOptions并根据需要申请查询选项
    // ...
    返回db.Persons.FilterDeleted();
}

请参见<一个href=\"http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#ODataQueryOptions\"相对=nofollow>查询中调用直接的选项的了解更多的是如何玩弄的对象。如果你读了整篇文章,请注意,在 [可查询] 属性是你的 [EnableQuery] 属性,因为本文是从OData的较低版本。

希望这点你在正确的方向,以达到你想要的东西。)


修改:对于一些信息过滤嵌套在$扩展条款:

OData的V4支持扩展内容过滤。这意味着你可以窝内的一个文件管理器扩展条款,是这样的:
GET API /用户()$展开;关注者?($顶部= 2; $选择=性别)。
在这种情况下,你又不得不让OData的处理它,或者自己处理探索 ODataQueryOptions 参数的选项:
内部控制器可以检查扩展选项,如果他们有嵌套过滤器与此code:

 如果(queryOptions.SelectExpand!= NULL){
    的foreach(在queryOptions.SelectExpand.SelectExpandClause.SelectedItems的SelectItem项){
        如果(item.GetType()== typeof运算(ExpandedNavigationSelectItem)){
            ExpandedNavigationSelectItem navigationProperty =(ExpandedNavigationSelectItem)项目;            //获取扩大了属性的名称(这样你可以控制你即将扩大其导航属性)
            VAR propertyName的=(navigationProperty.PathToNavigationProperty.FirstSegment为NavigationPropertySegment).NavigationProperty.Name.ToLowerInvariant();            //获取跳过和顶级嵌套过滤器:
            VAR跳跃= navigationProperty.SkipOption;
            VAR顶部= navigationProperty.TopOption;            / *在这里,您应该检索您的数据库中的实体,你
               将返回与嵌套过滤器所请求的扩展条款的结果
               ... * /
            }
        }
    }

I want to know if it's possible to pre-filter OData results in a WebAPI for items in the expand clause. I only want this to filter based on a predefined interface with a Deleted flag.

public interface IDbDeletedDateTime
{
    DateTime? DeletedDateTime { get; set; }
}

public static class IDbDeletedDateTimeExtensions
{
    public static IQueryable<T> FilterDeleted<T>(this IQueryable<T> self) 
        where T : IDbDeletedDateTime
    {
        return self.Where(s => s.DeletedDateTime == null);
    }
}

public class Person : IDbDeletedDateTime
{
     [Key]
     public int PersonId { get; set }
     public DateTime? DeletedDateTime { get; set; }
     public virtual ICollection<Pet> Pets { get; set; }
}

public class Pet : IDbDeletedDateTime
{
     [Key]
     public int PetId { get; set }
     public int PersonId { get; set }
     public DateTime? DeletedDateTime { get; set; }
}


public class PersonController : ApiController
{
    private PersonEntities db = new PersonEntities();

    [EnableQuery]
    // GET: api/Persons
    public IQueryable<Person> GetPersons()
    {
        return db.Persons.FilterDeleted();
    }
}

You can see that I'm very easily filtering deleted people. The problem comes when someone gets deleted Pets from a query like /api/Persons?$expand=Pets

Is there a way to check if this expansion of "Pets" is an IDbDeletedDateTime and filter them accordingly? Maybe there is a better way to approach this?

EDIT:

I tried to solve this based on what was picked up in this answer. I don't think it can be done, at least not in all scenarios. The only part of a ExpandedNavigationSelectItem that even looks like it is related to the filters is the FilterClause. This can be null when it has no filter, and it is only a getter property, meaning we can't set it with a new filter if we wanted to. Weather or not it is possible to modify a current filter is only covering a small use case that I'm not particularly interested in if I can't add a filter freshly.

I have an extension method that will recurse through all the expand clauses and you can at least see what the FilterOption is for each expansion. If anyone can get this 90% code fully realized, that would be amazing, but I'm not holding my breath on it.

public static void FilterDeletables(this ODataQueryOptions queryOptions)
{
    //Define a recursive function here.
    //I chose to do it this way as I didn't want a utility method for this functionality. Break it out at your discretion.
    Action<SelectExpandClause> filterDeletablesRecursive = null;
    filterDeletablesRecursive = (selectExpandClause) =>
    {
        //No clause? Skip.
        if (selectExpandClause == null)
        {
            return;
        }

        foreach (var selectedItem in selectExpandClause.SelectedItems)
        {
            //We're only looking for the expanded navigation items. 
            var expandItem = (selectedItem as ExpandedNavigationSelectItem);
            if (expandItem != null)
            {
                //https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
                //The documentation states: "Gets the Path for this expand level. This path includes zero or more type segments followed by exactly one Navigation Property."
                //Assuming the documentation is correct, we can assume there will always be one NavigationPropertySegment at the end that we can use. 
                var edmType = expandItem.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType;
                string stringType = null;

                IEdmCollectionType edmCollectionType = edmType as IEdmCollectionType;
                if (edmCollectionType != null)
                {
                    stringType = edmCollectionType.ElementType.Definition.FullTypeName();
                }
                else
                {
                    IEdmEntityType edmEntityType = edmType as IEdmEntityType;
                    if (edmEntityType != null)
                    {
                        stringType = edmEntityType.FullTypeName();
                    }
                }

                if (!String.IsNullOrEmpty(stringType))
                {
                    Type actualType = typeof(PetStoreEntities).Assembly.GetType(stringType);
                    if (actualType != null && typeof (IDbDeletable).IsAssignableFrom(actualType))
                    {
                        var filter = expandItem.FilterOption;
                        //expandItem.FilterOption = new FilterClause(new BinaryOperatorNode(BinaryOperatorKind.Equal, new , ));
                    }
                }

                filterDeletablesRecursive(expandItem.SelectAndExpand);
            }
        }
    };

    filterDeletablesRecursive(queryOptions.SelectExpand?.SelectExpandClause);
}

解决方案

Correct me if I understood wrong: you want to always filter the entities if they implement the interface IDbDeletedDateTime, so when the user wants to expand a navigation property you also want to filter if that navigation property implements the interface, right?

In your current code you enabled OData query options, with the [EnableQuery] attribute, so OData will handle the expand query option for you, and the Pets will not be filtered the way you want.

You have the option of implementing your own [MyEnableQuery] attribute, and override the ApplyQuery method: check there if the user has set the $expand query option and if so, check if the requested entity implements IDbDeletedDateTime and filter accordingly.

You can check here the code of the [EnableQuery] attribute and see that in the ApplyQuery method you have access to the object ODataQueryOptions that will contain all the query options set by the user (WebApi populates this object from the URI query string).

This would be a generic solution that you could use in all your controller methods if you are going to have several entities with that interface with your custom filtering. If you only want this for a single controller method, you can also remove the [EnableQuery] attribute, and invoke the query options directly in the controller method: add the ODataQueryOptions parameter to your method and handle the query options manually.

That would be something like:

// GET: api/Persons
public IQueryable<Person> GetPersons(ODataQueryOptions queryOptions)
{
    // Inspect queryOptions and apply the query options as you want
    // ...
    return db.Persons.FilterDeleted();
}

See the section Invoking Query Options directly to understand more how to play around with that object. If you read the entire article, be aware that the [Queryable] attribute is your [EnableQuery] attribute, since the article is from a lower version of OData.

Hope it points you in the right direction to achieve what you want ;).


EDIT: some information regarding nested filtering in $expand clause:

OData V4 supports filtering in expanded content. This means you can nest a filer inside an expand clause, something like: GET api/user()?$expand=followers($top=2;$select=gender). In this scenario, again you have the option to let OData handle it, or handle it yourself exploring the ODataQueryOptions parameter: Inside your controller you can check expand options and if they have nested filters with this code:

if (queryOptions.SelectExpand != null) {
    foreach (SelectItem item in queryOptions.SelectExpand.SelectExpandClause.SelectedItems) {
        if (item.GetType() == typeof(ExpandedNavigationSelectItem)) {
            ExpandedNavigationSelectItem navigationProperty =  (ExpandedNavigationSelectItem)item;

            // Get the name of the property expanded (this way you can control which navigation property you are about to expand)
            var propertyName = (navigationProperty.PathToNavigationProperty.FirstSegment as NavigationPropertySegment).NavigationProperty.Name.ToLowerInvariant();

            // Get skip and top nested filters:
            var skip = navigationProperty.SkipOption;
            var top = navigationProperty.TopOption;

            /* Here you should retrieve from your DB the entities that you
               will return as a result of the requested expand clause with nested filters
               ... */
            }
        }
    }

这篇关于的OData的WebAPI pre过滤扩展查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆