1

I have got two models. A Product model:

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

And a Categories model:

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

    public virtual Product Products { get; set; }
}

As you can see there is a relation between the models. One Product can have multiple categories. I'm able to query the model with http://localhost/Products?$expand=Categories.

However I want to filter the categories before they are returned. Main goal is to avoid that the api consumer can query categories where theIsPublic is set to false. How can I archive this inside the Web API controller?

My first attemp is not working because I need a IQueryable<Product> and it returns a 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;
            }
        }

Basically my question is: What is the correct way to write a OData-like $expand query in LINQ? If this is not possible, how else can I filter on en expanded navigation property?

Jan Hommes
  • 5,122
  • 4
  • 33
  • 45
  • Not sure if you're still in need of this, but I had the same problem and made a "solution" which I've posted as an answer to my [own question](http://stackoverflow.com/a/32945237/161250) about this issue. It might help you or anyone else who comes here looking for a solution. – Alex Oct 05 '15 at 09:32

1 Answers1

1

EF doesn't allow to filter included properties. (This was a highly voted feature request for EF but never implemented: Allow filtering for Include extension method)

So, EF doesn't support it. So I can only offer you these two hacks:

  • create a filtered view in the database, and map it to a different entity in EF. This is efficient, because the filtering will happen in the server.
  • in the controller's code, project the query to a new class, with the related collection filtered (by using Select(x=> new ...)), and make this projected result IQueryable with .AsQueryable extension method. In this way you'll returned a new queryable with related entities filtered as you wanted. This is more inefficient: it requires to recover the whole related collection from the DB server, filter it in the controller's method, and convert it to queryable

Obviously the first option is the "best hack". I think that unfortunately there is not a better solution. Perhaps some other hacks, with TVFs on the server or something like that.

JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • Thanks for clarifying this. Hopefully this feature will get into ef soon. For your first solution, is it possible to declare dynamic views? For example, for logged in users the category should be shown, for not logged in users not? For you second example, can you give a detailed example how to do this in web api? I tried it but always get an error when web api tries to serialize the result. – Jan Hommes May 13 '15 at 08:20
  • The nearest thing to your idea of "dynamic views" would be a TVF. It has poor supoort on Code First models, and only in the latest versions (form 6.1 I think). It has better support if your model is a diagram. Google "EF TVF" for more info. Your business logic should choose which parameter to pass to the TVF. For the second hack, take into account that once you have to make the original query with the `Include`. Then you must materialize it (for example with `ToList`), and then project it with `Select` so that this is LINQ to Objectsand not LINQ to Entities. Then return it as queryable. – JotaBe May 13 '15 at 08:38
  • Links are now broken, uservoice is now closed. – Andrew Hill Dec 08 '20 at 04:31