2015-10-14 261 views
3

我想知道是否有可能在扩展子句中为项目预先过滤ODAP结果。我只希望根据预定义的界面过滤掉一个已删除的标志。WebAPI OData预过滤扩展查询

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 /人删除宠物的问题就来了?$扩大=宠物

有没有一种方法来检查,如果这种扩张“宠物”是一个IDbDeletedDateTime并相应地进行筛选?也许有更好的方法来解决这个问题?

编辑:

我想基于什么在this answer捡起来解决这个问题。我不认为这是可以做到的,至少在所有情况下都是如此。 ExpandedNavigationSelectItem的唯一部分看起来像是与过滤器相关的是FilterClause。当它没有过滤器时,它可以为空,,它只是一个吸气属性,这意味着我们不能如果我们想要设置一个新的过滤器。天气还是不可能修改一个当前的过滤器只能覆盖一个小的用例,如果我不能新增一个过滤器,我并不特别感兴趣。

我有一个扩展方法,将递归通过所有展开子句,并且您至少可以看到每个展开的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); 
} 

回答

5

纠正我,如果我理解错了:你想,如果他们实现接口IDbDeletedDateTime总是过滤实体,所以当用户想扩大你也想过滤导航属性如果导航属性实现接口, 对?

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

你必须实现自己的[MyEnableQuery]属性的选项,并覆盖ApplyQuery方法:检查那里,如果用户已经设置$扩大查询选项,如果是这样,检查所请求的实体实现IDbDeletedDateTime并相应地进行筛选。

您可以检查here[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(); 
} 

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

希望它指出你在正确的方向来实现你想要的;)。


编辑

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

我不确定如果这会让我需要去哪里,但这是一个很好的答案。非常感谢!我可能会尝试一些今天的尝试 –

+0

它看起来像OData甚至不支持过滤扩展内容在它的当前实施无论如何。不幸的是,我不认为真的有办法解决这个问题。 –

+1

OData v4支持在$ expand子句中进行过滤。我编辑了我的答案,以解释如何手动处理这些嵌套过滤器(我不确定您是否可以嵌套OData支持的所有可能的过滤器,但至少包含基本过滤器,如$ skip,$ top for pagination,$ filter和$ select can嵌套)。 希望它对你有用。 (无论如何OData一天天在演变,所以我猜这很快会改变,并在不久的将来包含更多的选择)。 – elbecita

1

我向OData团队询问了这个问题,我可能会有一个可以使用的答案。我无法完全测试并得到使用,但看起来它能解决我遇到的问题,因为我能够解决这些问题。我想发布这个答案,以防万一这会帮助别人。

这就是说 ...它看起来有一个OData的顶部框架,似乎是在其相对婴儿期被称为RESTier正在由微软开发。正如示例所示,它似乎在OData之上提供了一个抽象层,允许使用这些类型的过滤器。

这看起来是上面的例子,在域对象的过滤器将被添加:

private IQueryable<Pet> OnFilterPets(IQueryable<Pet> pets) 
{ 
    return pets.Where(c => c.DeletedDateTime == null); 
} 

如果我避开实现这个逻辑,我将回到这个答案确认或否认使用此框架。

我从来没有能够实现这个解决方案,以知道它是否值得。在我的特殊使用案例中,要证明这个价值是非常困难的。对于新项目或者真正需要这些功能的人来说,这可能是一个很好的解决方案,但是我的具体使用案例对于将框架实现为现有逻辑具有挑战性。

你的里程可能会有所不同,这可能仍然是一个有用的框架来检查。

+0

你能用这个吗?在我的场景中,当需要通过特殊过滤来扩展导航属性X时,我继续使用前面答案的方法:我有一个检查expand语句的助手方法,如果它是X导航属性,则返回special-过滤的集合。当然,它没有什么可以通用的方式或容易地应用,所以这种RESTier的东西听起来很有前途。 – elbecita

+0

@elbecita不幸的是我没有。在研究尝试实施的过程中,有很多事情会自动进行,以至于我担心无法根据需要调整和调整控制器。我知道它说它有后备,但我真的不知道如何轻松使用它们。我也建立了太多的内容,给它一个真正的尝试将会严重影响到发展进程。那时它也处于初期阶段,并不想冒险进行大赌注。 –

2

Zachary,我有一个类似的需求,我可以通过编写一个算法来解决这个问题,该算法根据我的模型的属性为请求ODataUri添加额外的过滤。它检查根级实体的任何属性以及任何扩展实体的属性,以确定添加到OData查询的其他过滤器表达式。

OData v4支持在$ expand子句中进行过滤,但展开实体中的filterOption是只读的,因此您无法修改扩展实体的过滤器表达式。您只能检查扩展实体的filterOption内容。

我的解决方案是检查所有实体(root和展开式)的属性,然后在请求ODataUri的根过滤器中添加我需要的任何额外的$ filter选项。

下面是一个例子OData的请求URL:

/RootEntity?$expand=OtherEntity($expand=SomeOtherEntity) 

这是相同的OData请求URL我已经更新后:

/RootEntity?$filter=OtherEntity/SomeOtherEntity/Id eq 3&$expand=OtherEntity($expand=SomeOtherEntity) 

步骤来实现:

  1. 使用ODataUriParser将传入的Url解析为Uri对象

下面参见:

var parser = new ODataUriParser(model, new Uri(serviceRootPath), requestUri); 
var odataUri = parser.ParseUri(); 
  • 创建将从根向下遍历到所有展开的实体,并通过参考通过ODataUri(这样就可以更新它的方法根据需要检查每个实体)
  • 第一种方法将检查根实体并根据根实体的属性添加任何其他过滤器。

    AddCustomFilters(ref ODataUri odataUri); 
    

    AddCustomFilters方法将横向扩展实体和拨打AddCustomFiltersToExpandedEntity将继续向下遍历所有扩展实体添加任何必要的过滤器。

    foreach (var item in odatauri.SelectAndExpand.SelectedItems) 
    { 
        AddCustomFiltersToExpandedEntity(ref ODataUri odataUri, ExpandedNavigationSelectItem expandedNavigationSelectItem, string parentNavigationNameProperty) 
    } 
    

    的方法AddCustomFiltersToExpandedEntity应该调用本身,因为它遍历每个级别的扩展实体。

  • 要为您检查每一个实体
  • 创建您的附加滤波器的要求新的过滤器条款并覆盖在根级别的现有的过滤条款更新根过滤器。 ODataUri根级的$ filter过滤器有一个setter,所以它可以被重写。

    odataUri.Filter = new FilterClause(newFilterExpression, newFilterRange); 
    

    注:我建议创建使用BinaryOperatorKind.And一个新的过滤条款,使你的额外的过滤器表达式简单地附加到任何现有的过滤器表达式已经在ODataUri

    var combinedFilterExpression = new BinaryOperatorNode(BinaryOperatorKind.And, odataUri.Filter.Expression, newFilterExpression); 
    odataUri.Filter = new FilterClause(combinedFilterExpression, newFilterRange); 
    
  • 使用ODataUriBuilder来创建基于开放的更新
  • 一个新的URL见下:

    var updatedODataUri = new Microsoft.OData.Core.UriBuilder.ODataUriBuilder(ODataUrlConventions.Default, odataUri).BuildUri(); 
    
  • 替换请求URI与更新的URI。
  • 这允许OData的控制器完成处理使用经过更新的OData的地址,其包括刚才添加到该根级别的文件管理器的附加过滤器选项的请求。

    ActionContext.Request.RequestUri = updatedODataUri; 
    

    这应该为您提供添加所需过滤选项的功能,并且100%确定您没有错误地更改OData Url结构。

    我希望这可以帮助你实现你的目标。

    +0

    这是一个很好的答案!但是,要弄清楚这些作品应该存在的位置有点棘手。我很快就会试一试。 –

    +0

    我稍微回顾一下。我大部分都失望了。 OData uri何时何地应该更新?过滤器或什么? –

    2

    我有类似的问题,我设法解决它使用Entity Framework Dynamic Filters

    在你的情况,你可以创建一个过滤器,过滤掉所有被删除的记录,这样的:

    你的DbContext OnModelCreating方法

    modelBuilder.Filter("NotDeleted", (Pet p) => p.Deleted, false); 
    

    此过滤器,然后将应用每次查询您的宠物的时间直接或通过OData的$ expand收集。您当然可以完全控制过滤器,并且可以手动或有条件地禁用它 - 它在动态过滤器文档中有介绍。

    +0

    太棒了!他们也在接口上工作,所以我可以做'(IDbDeletedDateTime deletable)=> deletable.DeletedDateTime == null'。你可以让它们不是全球的!如果它运作良好,这是非常好的!谢谢。 –

    +0

    这不适用于EF数据库优先,这是我现在正在使用的可悲的。 https://github.com/jcachat/EntityFramework.DynamicFilters/issues/12 –

    +0

    @ZacharyDow听起来很难过,但很高兴知道。我在EF Code First中使用了这个解决方案 –