如何在 Web-Api 控制器中过滤 odata $expand 查询

How to filter an odata $expand query in a Web-Api controller

我有两个模型。 A 产品型号:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public virtual ICollection<Categories> Categories { get; set; }
}

还有一个类别模型:

public class Categories
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsPublic { get; set; }

    public virtual Product Products { get; set; }
}

如您所见,模型之间存在关联。一个产品可以有多个类别。我可以使用 http://localhost/Products?$expand=Categories 查询模型。

但是我想在返回类别之前对其进行过滤。主要目标是避免 api 消费者可以查询 IsPublic 设置为 false 的类别。我怎样才能在 Web API 控制器中存档它?

我的第一次尝试没有成功,因为我需要一个 IQueryable<Product> 而它 returns 一个 IQueryable<Categories>:

        [HttpGet]
        [EnableQuery]
        public IQueryable<Product> Get(ODataQueryOptions<Product> odataQueryOptions)
        {
            if (odataQueryOptions.SelectExpand.RawExpand == "Categories")
            {
                var p = db.Products.First();
                var result = db.Entry(p).Collection(x => x.Categories).Query().Where(x => x.IsPublic == true).AsQueryable();
                return result;
            }
            else
            {
                return db.Products;
            }
        }

基本上我的问题是:在 LINQ 中编写类似 OData 的 $expand 查询的正确方法是什么?如果这不可能,我还能如何过滤扩展导航 属性?

EF 不允许过滤包含的属性。 (这是一个高度投票 feature request for EF but never implemented: Allow filtering for Include extension method

因此,EF 不支持它。所以我只能给你这两个技巧:

  • 在数据库中创建过滤视图,并将其映射到 EF 中的不同实体。这很有效,因为过滤将在服务器中进行。
  • 在控制器的代码中,将查询投影到新的 class,过滤相关集合(通过使用 Select(x=> new ...)),并使此投影结果 IQueryable.AsQueryable 扩展方法。通过这种方式,您将返回一个新的可查询对象,其中包含根据需要过滤的相关实体。这个比较低效:需要从DB server中恢复整个相关的collection,在controller的方法中过滤,转换成queryable

显然第一个选项是"best hack"。我认为不幸的是没有更好的解决方案。也许还有一些其他黑客攻击,在服务器上使用 TVF 或类似的东西。