WebAPI OData预过滤扩展查询

Zac*_*Dow 3 c# odata asp.net-web-api

我想知道是否可以在扩展子句中的项目的WebAPI中预先过滤OData结果.我只希望根据带有Deleted标志的预定义接口进行过滤.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以看到我很容易过滤被删除的人.有人被删除的问题来自/ api/Persons这样的查询宠物?$ expand = Pets

有没有办法检查"Pets"的扩展是否为IDbDeletedDateTime并相应地过滤它们?也许有更好的方法来解决这个问题?

编辑:

我试着根据这个答案中提到的内容来解决这个问题.我不认为可以做到,至少在所有情况下都不行.ExpandedNavigationSelectItem甚至看起来它与过滤器有关的唯一部分是FilterClause.当它没有过滤器时,它可以为null,并且它只是一个getter属性,这意味着如果我们想要的话,我们不能用新的过滤器设置它.天气与否可以修改当前过滤器只涵盖一个我不是特别感兴趣的小用例,如果我不能新添加过滤器.

我有一个扩展方法,它将遍历所有的扩展子句,你至少可以看到每个扩展的FilterOption是什么.如果有人能够完全实现90%的代码,那将是惊人的,但我并没有屏住呼吸.

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);
}
Run Code Online (Sandbox Code Playgroud)

elb*_*ita 5

如果我理解错误,请纠正我:如果他们实现了接口IDbDeletedDateTime,你想要总是过滤实体,所以当用户想要扩展导航属性时,你也想过滤那个导航属性是否实现了接口,对吗?

在您当前的代码中,您使用该[EnableQuery]属性启用了OData查询选项,因此OData将为您处理扩展查询选项,并且不会按您希望的方式过滤宠物.

您可以选择实现自己的[MyEnableQuery]属性,并覆盖该ApplyQuery方法:检查用户是否设置了$ expand查询选项,如果是,请检查所请求的实体是否实现IDbDeletedDateTime并相应地进行过滤.

您可以在此处[EnableQuery]查看属性的代码,并在ApplyQuery方法中查看您可以访问ODataQueryOptions将包含用户设置的所有查询选项的对象(WebApi从URI查询字符串中填充此对象).

这将是一个通用的解决方案,您可以在所有控制器方法中使用它,如果您要在自定义过滤中使用该接口的多个实体.如果只需要单个控制器方法,则还可以删除该[EnableQuery]属性,并直接在控制器方法中调用查询选项:将ODataQueryOptions参数添加到方法中并手动处理查询选项.

这将是这样的:

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

请参阅直接调用查询选项部分以了解有关如何使用该对象的更多信息.如果您阅读整篇文章,请注意该[Queryable]属性是您的[EnableQuery]属性,因为该文章来自较低版本的OData.

希望它指出你正确的方向,以实现你想要的;).


编辑:有关$ expand子句中嵌套过滤的一些信息:

OData V4支持在扩展内容中进行过滤.这意味着你可以在扩展子句中嵌入一个文件管理器,例如:GET api/user()?$ expand = followers($ top = 2; $ select = gender).在这种情况下,您再次可以选择让OData处理它,或者自己探索ODataQueryOptions参数来处理它:在控制器内部,您可以检查扩展选项以及它们是否具有此代码的嵌套过滤器:

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
               ... */
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)