Probably the most frequently requested addition to OData is better support for derived types in queries.
What does that mean exactly?
Today if you have a model like this:
Assuming you have a People set - as is usually but not always the case - and you try to expose it via a DataService here: http://server/service.svc/People it will fail.
This is because Data Services recognizes that the OData protocol doesn't provide rich enough support for querying derived types, so it fails early.
What is missing from OData?
(For the sake of brevity we'll use this ~ as short hand for http://server/service.svc from now on).
Given this setup all Employee objects should live in the same feed as - their base type - Person.
So if the Person feed is here: ~/People
Employee 41 will be found here: ~/People(41)
Great. So far so good.
What though if you want to retrieve the manager for Employee 41?
This: ~/People(41)/Manager won't work, because as far as the OData protocol is concerned the ~/People(41) uri segment is a Person, which as you can see from the model doesn't have a Manager property or navigation.
Not only that you can't reference derived properties or navigations in any query options either:
So something like this: ~/People/?$filter=Building eq '18' OR Firstname eq 'Bill'
Which is trying to find Employees in building 18 or People called Bill, won't work, again because OData sees the Segment type as Person and Building isn't defined on Person.
These two limitations really restrict the sorts of models you can expose in OData.
To remove these limitations we need two things:
- A way to change the url segment type, by filtering a base type feed to a derived type feed, so that derived navigations and properties can be accessed.
- A way to access derived properties and navigations in filters, projections, orderbys and expand query options without previously changing the url segment type.
Option 1 - !Employee:
We could add an ability to filter like this:
Which would filter the feed to contain only instances of employee or a subtype of employee and change the type of the segment from Person to Employee.
At which point you could either use a key selector directly:
or you could access derived properties and navigations:
you could also shape & filter the results using query options that now have access to the properties of Employee:
~/People!Employee/?$filter=ManagerFirstname eq 'Bill' OR Firstname eq 'Bill'
We'd also give you the ability to get to the derived type in a query option like this:
~/People/?$filter=!EmployeeManagerFirstname eq 'Bill' OR Firstname eq 'Bill'
You might be wondering why leaving the segment type unchanged and changing the type in the query option like this is useful.
If you compare the intent of these two urls, it should become clear:
(1): ~/People!Employee/?$filter=ManagerFirstname eq 'Bill' OR Firstname eq 'Bill'
(2): ~/People/?$filter=!EmployeeManagerFirstname eq 'Bill' OR Firstname eq 'Bill'
Uri (1) returns employees that are either called Bill or have a manager called Bill.
Uri (2) returns employees who have a manager called Bill or *people* who are called Bill.
Option 2 - OfType:
Another option is to add a system function called OfType(..), that takes the target type name as a parameter. And allow functions to be appended to URI segments and used in query options.
So something like this:
would return just employees, and allow access to Employee properties etc.
~/People/OfType('Employee')/?$filter=ManagerFirstname eq 'Bill'
would return just employees managed by Bill.
If you wanted to access by key, you would simple add a key in the normal way:
And now you can continue appending OData uri fragments in the normal way:
to access Employee 41's manager and raw ($value) Building respectively.
You could also use the function like this:
~/People/?$filter=OfType('Employee')/Manager/Firstname eq 'Bill'
This final example does feel a little weird, at least if you dig into the semantics, because it is actually a null propagating cast operator!
If we allowed you to apply OfType(..) to uris that represent a single entry as well as uris that represent feeds, like this:
there is a chance for ambiguity.
Take this example:
this is a valid OData uri today, that says get Person 1's friends and look for Person 2 in that collection.
All that needs to change to make this collide with OfType(..), is for the navigation property name to change from Friends to OfType.
So if we ever supported OfType on a segment identifying a single entry we would need a way of disambiguating.
Clearly OfType(..) is a 'system' function so the obvious choice would be to further embrace the $ prefix.
The rule could be: if there is any ambiguity, pick the user navigation property. Then if you really need to use the system OfType(..) function you need to prepend the $ prefix, like this:
Pros and Cons?
Option 1 has these strengths:
- It is very simple.
- It avoids quoting type names.
- There are never any ugly double bracket combos i.e. ('type')(key).
- It avoids possible ambiguity with navigation properties.
Option 2 on the other hand:
- Is nicely aligned with the existing IsOf('TypeName') function which can be used today to narrow a set of results like this: ~/People/?$filter=IsOf('Employee')
- Unlike the !Employee option, this approach is more widely applicable. You can simply think of OfType as a (system) function, that we allow you to append to correctly typed URIs or inside query options, sort like CLR extensions methods. This approach would lay a foundation for a future world where custom functions can be appended to URIs too.
- If OfType is simply a function, then creating a way to change the type of a single entry, rather than a feed, like this: ~/People(41)/OfType('Employee')
is as simple as adding a new overload.
Perhaps we can get the benefits of both by thinking of !Employee as simply a short hand notation for OfType('Employee')?
As you can see we've come up with two options to add better support for Derived types in OData queries.
Which do you prefer? And why? Perhaps you have another suggestion?
Whatever you think, please tell us all about it on the OData Mailing List.