Web Api $使用过滤器扩展IQueryable

时间:2015-12-07 13:05:22

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

我有一个设置,我得到一个WebApi OData服务返回:客户。退回客户的代码是:

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

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

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

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

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

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

odata/customers?$expand=contacts

这也很有效。我收到了所有相关联系人的客户。但是,正如您猜测的那样,我希望只接收具有IsActive的联系人。 IQueryable功能为我提供了所有结果。

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

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

3 个答案:

答案 0 :(得分:4)

EntityFramework.DynamicFilters是我所知道的实体框架最好的工具之一。它跳进了经常要求的但是直到EF6从未实现过滤的Incude s功能的空白。它依赖于EF的拦截API,并且在暴露非常简单的界面的同时完成了修改表达式的繁重工作。

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

using EntityFramework.DynamicFilters;

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

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

您想要所有客户吗?您可以通过执行

简单地按照上下文实例关闭过滤器
context.DisableFilter("CustomerActive");

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

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

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

这是因为在SQL中INNER JOINParent之间有Child,而Parent上的谓词评估为falseOUTER JOIN会给出预期的行为,但当然我们不能要求此库 智能。

答案 1 :(得分: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
        }
      ]
    }
  ]
}

答案 2 :(得分:0)

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

您可以拥有客户联系 视图,它们只是原始表格的过滤版本。

回到C#方面,您的模型可以直接引用 Views ,就像它们是表格一样。

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