c# - WebAPI OData 预过滤扩展查询

标签 c# asp.net-web-api odata

我想知道是否可以在 WebAPI 中为扩展子句中的项目预过滤 OData 结果。我只希望它基于带有 Deleted 标志的预定义接口(interface)进行过滤。

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();
    }
}

您可以看到我很容易过滤已删除的人。当有人从 /api/Persons?$expand=Pets

这样的查询中删除 Pets 时,问题就来了

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

编辑:

我试图根据在 this answer 中找到的内容解决这个问题.我不认为这是可以做到的,至少不是在所有情况下。 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);
}

最佳答案

如果我理解有误,请纠正我:如果实体实现了接口(interface) IDbDeletedDateTime,您希望始终过滤实体,因此当用户想要扩展导航属性时,您也希望过滤该导航属性实现接口(interface),对吧?

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

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

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

这将是一个通用的解决方案,如果您要让多个实体与您的自定义筛选接口(interface)具有该接口(interface),则您可以在所有 Controller 方法中使用该解决方案。如果您只想将其用于单个 Controller 方法,您还可以删除 [EnableQuery] 属性,并直接在 Controller 方法中调用查询选项:添加 ODataQueryOptions 参数到您的方法并手动处理查询选项。

那会是这样的:

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

参见 Invoking Query Options directly 部分了解更多如何使用该对象。如果您阅读整篇文章,请注意 [Queryable] 属性是您的 [EnableQuery] 属性,因为这篇文章来自较低版本的 OData。

希望它能为您指明正确的方向以实现您的目标 ;)。


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

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

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
               ... */
            }
        }
    }

关于c# - WebAPI OData 预过滤扩展查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33126251/

相关文章:

c# - 有没有办法使用 FileHelpers 库进行现场排序?

c# - 将大型项目从 VB.Net 迁移到 C# 的技巧?

javascript - 将整数列表发送到 REST API 会抛出 415(不支持的媒体类型)

asp.net-mvc - 如何在 Json .NET Web API 中 POST 但 JsonIgnore GET 时读取属性

odata - 如何发出异步 OData .create() 请求?

c# - Javascript DATE 和 C# date - 什么是最好的解决方案?

c# - 从任何应用程序调用默认命令行解析方法

c# - 中间带有属性路由的 Web Api 可选参数

c# - 如何使用 OData.Delta 修补/更新外键?

asp.net - 带复合键的 Odata v3 Web Api 导航