【问题标题】:Web Api $extend IQueryable with filterWeb Api $extend IQueryable 与过滤器
【发布时间】:2015-12-07 13:05:21
【问题描述】:

我有一个设置,其中我有一个 WebApi OData 服务,它返回:客户。返回客户的代码是:

public IHttpActionResult GetCustomers(ODataQueryOptions<Customer> queryOptions)
{
    return Ok(context.Customers.Where(i => i.IsActive).AsQueryable());
}

因此,GetCustomers 方法返回所有活动客户的 IQuerable 结果。出于历史目的,我们将所有客户保留在数据库中,但是当客户被删除时,我们将 IsActive 字段设置为 false。

OData 设置是使用简单的 builder.EntitySet 创建的,用于为实体构建 Url。

EntitySetConfiguration<Customer> customers = builder.EntitySet<Customer>("customers");

这完美无缺。我有一个 Angular 前端,它使用 $http 调用来接待客户等。

但是,客户可以在数据库中包含相关联系人。为了在 Angular 前端获取联系人,我使用了 OData 的 $extend 功能:

odata/customers?$expand=contacts

这也很好用。我收到了所有相关联系人的客户。但是,正如您已经猜到的那样,我只想接收应该返回具有 IsActive 的联系人。 IQueryable 功能将所有结果返回给我。

我知道我可以使用单独的 Odata 调用来获取联系人,但我真的很想使用 $expand 功能在一次调用中获取所有数据。我知道我也可以在客户端进行过滤(使用:$filter)。但我想在 WebApi 部分正确设置它,因此客户端不必关心过滤不活动的结果。

我似乎无法弄清楚如何正确实现这一目标。有人可以帮我走上正轨吗?

【问题讨论】:

标签: c# angularjs entity-framework asp.net-web-api odata


【解决方案1】:

EntityFramework.DynamicFilters 是我所知道的 Entity Framework 最好的工具之一。它跳入了经常请求但直到 EF6 从未实现过滤Incudes 的功能的空白。它依赖于 EF 的拦截 API,并在公开一个非常简单的接口的同时完成了修改表达式的繁重工作。

在你的情况下,你可以做的是这样的:

using EntityFramework.DynamicFilters;

// In OnModelCreating (DbContext)
modelBuilder.Filter("CustomerActive", (Customers c) => c.IsActive);

就是这样!现在无论在哪里查询Customers,无论是直接、通过导航属性还是在Includes 中,都会将谓词添加到查询中。

您想要所有客户吗?您可以通过以下方式简单地关闭每个上下文实例的过滤器

context.DisableFilter("CustomerActive");

到目前为止,我只发现了一个故障(或警告)。如果有两个实体,ParentChild,并且Parent 上有一个不返回任何记录的过滤器,那么这个查询...

context.Children.Include(c => c.Parent)

... 不返回任何内容。但是,从查询的形式来看,我希望它返回 Child 具有空父实体的实体。

这是因为在 SQL 中,ParentChild 之间有一个 INNER JOINParent 上的谓词计算结果为 falseOUTER JOIN 会给出预期的行为,但我们当然不能要求这个库那么聪明。

【讨论】:

    【解决方案2】:

    数据模型:

    public class Customer
    {
        public int Id { get; set; }
        public bool IsActive { get; set; }
        public ICollection<Contact> Contacts { get; set; }
    }
    
    public class Contact
    {
        public int Id { get; set; }
        public bool IsActive { get; set; }
    }
    

    带有固定数据的控制器:

    public class CustomersController : ODataController
    {
        private List<Customer> customers = new List<Customer>
        {
            new Customer { Id = 1, IsActive = false },
            new Customer { Id = 2, IsActive = true,
                Contacts = new List<Contact>
                {
                    new Contact { Id = 101, IsActive = true },
                    new Contact { Id = 102, IsActive = false },
                    new Contact { Id = 103, IsActive = true },
                }
            }
        };
    
        [EnableQuery]
        public IHttpActionResult Get()
        {
            return Ok(customers.Where(c => c.IsActive).AsQueryable());
        }
    }
    

    请注意,一个客户处于活动状态,并且该客户有 2 个(共 3 个)活动联系人。

    最后,配置您的 OData 服务:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            var builder = new ODataConventionModelBuilder();
            builder.EntitySet<Customer>("customers");
    
            config.MapODataServiceRoute(
                routeName: "OData",
                routePrefix: null,
                model: builder.GetEdmModel());
        }
    }
    

    现在调用服务如下:

    GET http://host/customers?$expand=Contacts($filter=IsActive eq true)
    

    您应该会收到类似于以下内容的有效负载:

    {
      "@odata.context": "http:/host/$metadata#customers",
      "value": [
        {
          "Id": 2,
          "IsActive": true,
          "Contacts": [
            {
              "Id": 101,
              "IsActive": true
            },
            {
              "Id": 103,
              "IsActive": true
            }
          ]
        }
      ]
    }
    

    【讨论】:

    • 但是这是一个非常完整的答案,我已经想到了这一点,但我想在服务器端执行操作,而不是在客户端 $filter。请参阅问题中的最后一句话:“我知道我也可以在客户端进行过滤(使用:$filter)。但我想在 WebApi 部分正确设置它,因此客户端不必关心关于过滤不活动的结果。”
    • 过滤正在发生在服务器端。客户端只是请求使用$expand=Contacts($filter=IsActive eq true) 表达式进行某种过滤。您是说您不想将该表达式添加到请求 URI 中吗?
    • 你是对的,过滤是在服务器上完成的。也许我措辞不够清楚,我希望客户端(生成 URI)不知道 IsActive 字段。 DynamicFilters 解决方案对我来说是最佳选择。
    【解决方案3】:

    一种可能的解决方案是添加 Views 来表示您实际想要公开的数据。

    您可以拥有 CustomerContact Views,它们只是原始表格的过滤版本。

    回到 C# 端,您的模型可以直接引用 视图,就好像它们是表一样。

    好消息是它们将被视为表,所有延迟加载、导航属性和数据库端过滤仍将像引用原始表一样工作。

    【讨论】:

    • 使用视图可能是一个可行的选择,但我不喜欢首先在实体框架代码中使用视图的想法的一般感觉。我认为使用@GertArnold 的解决方案更适合我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多