About a week ago we outlined some options for enhancing OData support for querying derived types.

From there the discussion moved to the OData Mailing list, where the community gave us very valuable feedback:

  • Everyone seemed to like the idea in general - evidenced by the fact that all debate focused on syntax.
  • Most liked the idea of the short hand syntax for OfType.
  • However Robert pointed out that using the !Employee syntax is weird because ! implies NOT to a lot of developers.
  • Given this some suggested alternatives like using @ or [].
  • Erik wondered if we could simply use the name of the type in a new segment, dealing with any ambiguity by picking properties and navigations first.
  • Robert pointed out that it would be hard to know when to use IsOf and when to use OfType.

 

Feedback Takeaways

Well we just sat down and discussed all this, so what follows is an updated proposal taking into account your feedback.

Clearly the '! means NOT' issue is a show stopper, thanks for catching this early Robert.

That option is out.

Most seem to want a short-hand syntax - the complicating factor though is that we really don't like ambiguity - so whatever syntax we choose must be precise and unambiguous.

 

Using []'s:

We considered using '[]' instead of ! like this:

~/People[Employee]
~/People[Employee](2)/Manager
~/People(2)/[Employee]/Manager
~/People(2)/Mother[Employee]/Manager
~/People(2)/Mother[Employee]/Manager/Father[Employee]/Reports

and something like this in query segments:

~/People/?$filter=[Employee]/Building eq 'Puget Sound Building 18'
~/People/?$expand=[Employee]/Manager
~/People/?$orderby=[Employee]/Manager/Firstname

This is okay but it does have at least three issues:

  • It makes OData URIs a little harder to parse.
  • It makes OData URIs harder to read.
  • It makes []() combo segments reasonably common.

Not to mention it is the return of [] which has a colorful history in the OData protocol: early versions of Astoria used [] for keys!

 

Using Namespace Qualified Type Names:

We think a better option is to use Namespace Qualified Names.

The Namespace qualification removes any chance of ambiguity with property and navigation names. So each time an OfType or Null propagating cast is required, you simply create a new segment with the Namespace Qualified type name.

If Employee is in the HR namespace the earlier queries would now look like this:

~/People/HR.Employee
~/People/HR.Employee(2)/Manager
~/People(2)/HR.Employee/Manager
~/People(2)/Mother/HR.Employee/Manager
~/People(2)/Mother/HR.Employee/Manager/Father/HR.Employee/Reports

and something like this in query segments:

~/People/?$filter=HR.Employee/Building eq 'Puget Sound Building 18'
~/People/?$expand=HR.Employee/Manager
~/People/?$orderby=HR.Employee/Manager/Firstname

This does make the choice of your namespace more important than it is today, because it is more front and centre, but the final result is:

  • URIs that are more readable.
  • Casting is always done in a separate segment which makes it feel more like a function that operates on the previous segment.
  • [] is preserved for later use.

So this seems like a good option.

 

Impact on the protocol

How does it work across the rest of the protocol?

 

404's

If you try something like this:

~/People(11)/HR.Employee

And person 11 is not an employee, you will get a 404. Indeed if you try any other operations on this uri you will get a 404 too.

 

Composition

After using the Namespace Qualified Type name to filter and change segment type all normal OData uri composition rules should apply. So for example these queries should be valid:

~/People/HR.Employee/$count/?$filter=Firstname eq 'Bill'
~/People(6)/HR.Employee/Reports/$count
~/People(6)/HR.Employee/Reports(56)/Firstname/$value

Also you should be able to apply type filters to service operations that support composition:

~/GetPeopleOlderThanSixty/HR.Employee

 

$filter

As explained in the earlier post this:

~/People/?$filter=HR.Employee/Manager/Firstname eq 'Bill'

Will include only employees managed by Bill.

Whereas this:

~/People/?$filter=HR.Employee/Manager/Firstname eq 'Bill' OR Firstname eq 'Bill'

Will include anyone called Bill or any *employee* managed by Bill.

 

$expand

$expand will expand related results where possible. So this:

~/People/?$expand=HR.Employee/Manager

Would include managers for each employee using the standard OData <link><m:Inline> approach, and non employees would have no Manager <link>.

 

$orderby

This query:

~/People/?$orderby=HR.Employee/Manager/Firstname

Would order by people by Manager name. And because HR.Employee is a null propagating cast any entries which aren't Employees would be ordered as if their Manager's Firstname were null.

 

$select

Given this query:

~/People/?$select=Firstname, HR.Employee/Manager/Firstname

All Employee's will contain Firstname and Manager/Firstname. All non-employees will contain just Firstname.

You might expect people to have a NULL Manager/Firstname, but there is a real difference between having a NULL property value and having no property!

 

Inserts - POST

Today when inserting into a Collection that allows derived types you must specify the type using the entry's category's term.

However if you post to a type specialized uri like this:

~/People/HR.Employee

The server may be able to infer the exact type being created.

So if there are no types derived from HR.Employee, the server can unambiguously infer that you want to insert an employee, without the request specifying a category.

On the other hand if there are types derived from HR.Employee the request must specify the type via the category.

Finally if the type is specified via the category and it is not a HR.Employee (or derived from HR.Employee) the request will fail.

 

Deep Inserts

OData already supports 'deep inserts'. A 'deep insert' creates an entity and builds a link to an existing entity. So posting here:

~/People(6)/Friends

Inserts a new person and creates a 'friends' link with Person 6.

The same type specification rules apply for deep inserts too, so posting here:

~/People(6)/Friends/HR.Employee

Will succeed only if the type is unambiguous and allowed (i.e. it is an Employee or derived from Employee).

 

Updates - PUT / MERGE / PATCH

You can't change type during an update. So putting here:

~/People(6)/HR.Employee

Will only succeed if Person 6 is an Employee.

The same applies for MERGE and will apply to PATCH when supported.

 

Deletes - DELETE

Delete is as you would expect, and will work unless the URI 404's.

 

Summary

Clearly adding better support for derived types to OData is valuable. And it seems like the proposal is starting to take shape - thanks in large part to your feedback!

What do you think?

The best place to tell us is on the OData Mailing List.

Alex James
Program Manager
Microsoft