I have a setup where I've get a WebApi OData service which returns: Customers. The code for returning the customers is:
public IHttpActionResult GetCustomers(ODataQueryOptions<Customer> queryOptions)
{
return Ok(context.Customers.Where(i => i.IsActive).AsQueryable());
}
So the GetCustomers method returns an IQuerable result of all active customers. For history purposes we leave all customers in the database, but when a customer is removed, we set the IsActive field to false.
The OData setup is created using a simple builder.EntitySet to build the Url for the entities.
EntitySetConfiguration<Customer> customers = builder.EntitySet<Customer>("customers");
This works flawlessly. I have an Angular front-end which uses $http calls to receive the customers, etc.
However a customer can contain related contacts in the database. To get the contacts in the Angular Frontend, I use the $extend functionality of OData:
odata/customers?$expand=contacts
This also works great. I receive the customers with all related contacts. However as you've guessed I would like to receive only contacts which have IsActive should be returned. And the IQueryable functionality gives me all results back.
I understand I can use the seperate Odata call to get the contacts, but I really would like to use the $expand features to get all data in one call. I know I can also do the filtering on the client side (with: $filter). But I'd like to setup this correctly in the WebApi part, so the client does not have to care about filtering inactive results back.
I can't seem to figure out how to achieve this correctly. Can somebody help me get on the right track?
One possible solution is to add Views to represent the data you actually want to expose.
You can have Customer and Contact Views which are just filtered versions of the original table.
Back on the C# side, your models can directly reference the Views as if they were tables.
The nice thing is that they will be treated just as tables, all lazy loading, navigation properties, and database side filtering will still work as if you were referencing the original tables.
EntityFramework.DynamicFilters is one of the greatest tools for Entity Framework that I know. It jumped into the gap of the often-requested but up to EF6 never implemented feature of filtered
Incude
s. It leans on EF's interception API and does the heavy lifting of modifying expressions while exposing a very simple interface.In your case, what you can do is something like this:
That's all! Now everywhere where
Customers
are queried, be it directly, through navigation properties or inInclude
s, the predicate will be added to the query.Do you want all customers? You can simply turn the filter off per context instance by doing
There's only one glitch (or caveat) I discovered so far. If there are two entities,
Parent
andChild
and there is a filter onParent
that doesn't return any records, then this query ...... doesn't return anything. However, from the shape of the query, I would expect it to return
Child
entities with empty parents.This is because in SQL there is an
INNER JOIN
betweenParent
andChild
and a predicate onParent
that evaluates tofalse
. AnOUTER JOIN
would give the expected behavior, but of course we can't demand from this library to be that smart.Data model:
Controller with canned data:
Note that one Customer is active, and that Customer has 2 (out of 3) active Contacts.
Finally, configure your OData service:
Now call the service as follows:
You should receive a payload similar to this: