Flattened Results in OData

Version 1.0
Date: 2013-1-25
Author: Mike Pizzo ()

Overview

By default, OData represents graphs in ATOM or JSON through nesting expanded child entries within parent entries. However, there are some scenarios in which a flattened representation of the graph is more appropriate, for example:

1) When child elements are repeated within the graph

2) When a result may include related child entries without, or in a different order to, their parent entries (for example, when representing individual changes to a graph).

This document defines the rules around a flattened result by first introducing the general concept of an entryId, and then using that concept to represent relationships between entries of a flattened result.

Entry IDs

EntryIDs provide a general way to reference entities by ID without serializing the entity itself.

Requesting Entry IDs

In OData V4, the client can retrieve Entry IDs in place of an entity by requesting $id in the $select.

For example, a client could specify the following request to select a set of customers and the IDs of their related orders:

~Customers?$expand=Orders($select=$id)

$id MUST NOT be combined with any other properties, but MAY be combined with expanded navigation properties, for example;

~Customers?$expand=Orders($select=$id,Order_Details;$expand=Order_Details)

would select Customers, the IDs of their orders, and the order details associated with each Order ID, while;

~Customers?$expand=Orders($select=$id,Order_Details;$expand=Order_Details($select=$id))

would select Customers, the IDs of their orders, and just the IDs of each order detail.

Representing Entry IDs in Atom

In ATOM, Entry IDs are returned as an entryId, defined in the OData metadata namespace.

entryId = element m:entry-id {

attribute ref { atomUri }

[,element link]*

}

The entryId element appears in place of a regular atom entry element, either as a direct child of the <m:inline> (for relationships with maximum cardinality of 1) or within the <feed> element (for relationships with a cardinality of many).

The entryId element MUST have a nested atom link element representing each expanded child navigation property.

Example: Retrieving Entry IDs in ATOM

The example below shows an ATOM payload selecting Customers and the ids for their related Orders and Order_Details:

~Customers('ALFKI')?$expand=Orders($select=$id,Order_Details;$expand=Order_Details($select=$id))

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>

<feed xml:base="http://northwinddelta.cloudapp.net/DeltaService.svc/" xmlns:d="http://odata.org" xmlns:m="http://odata.org/metadata" xmlns="http://www.w3.org/2005/Atom">

<id>http://DeltaService.svc/Customers</id>

<link rel="self" title="Customers" href="Customers"/>

<entry>

<id>http://DeltaService.svc/Customers('ALFKI') </id>

<title type="text" />

<updated>2011-02-16T01:00:25Z</updated>

<author><name /></author>
<link rel="edit" title="Customer" href="Customers('ALFKI')" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Orders" type="application/atom+xml;type=feed" title="Orders" href="Customers('ALFKI')/Orders">

<m:inline>

<feed>

<m:entry-id ref="http://DeltaService.svc/Orders(10643)">

<link rel="http:/schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
type="application/atom+xml;type=feed" title="Order_Details"
href="Orders('10643')/Order_Details">

<m:inline>

<feed>

<m:entry-id ref="http://DeltaService.svc/OrderDetails(OrderId=10643,ProductId=28)"/>

<m:entry-id ref="http://DeltaService.svc/OrderDetails(OrderId=10643,ProductId=39)"/>

</feed>

</m:inline>

</link>

</m:entry-id>

<m:entry-id ref="http://DeltaService.svc/Orders(10692)">

<link rel="http:/schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
type="application/atom+xml;type=feed" title="Order_Details"
href="Orders('10692')/Order_Details">

<m:inline>

<feed>

<m:entry-id ref="http://DeltaService.svc/OrderDetails(OrderId=10692,ProductId=63)"/>

</feed>

</m:inline>

</link>

</m:entry-id>

</feed>

</m:inline>

</link>

<category term="NorthwindModel.Customer"
scheme="http://odata.org/scheme" />
<content type="application/xml">
<m:properties>
<d:CustomerID>ALFKI</d:CustomerID>

<d:CompanyName>Alfreds Futterkiste</d:CompanyName>

<d:ContactName>Maria Anders</d:ContactName>

<d:ContactTitle>Sales Representative</d:ContactTitle>

</m:properties>
</content>
</entry>
</feed>

Representing Entry IDs in JSON

In JSON, Entry IDs are returned as an entryId object with the following required property:

odata.entryId – The id of the entry. This property MUST appear before any property or property annotations.

The entryId object appears in place of a regular JSON entry object, either as the value of the navigation property (in the case of a max cardinality of 1) or as a member of the navigation property's array (in the case of a cardinality of many).

In addition to the odata.entryId property, the entryId object MUST have a property for each expanded child navigation property.

Example:Retrieving Ids in JSON

The example below shows a JSON payload selecting Customers and the ids for their related Orders and Order_Details:

~Customers('ALFKI')?$expand=Orders($select=$id;$expand=Order_Details($select=$id))

{

"odata.id":"http://DeltaService.svc/Customers('ALFKI')'",
"CustomerID":"ALFKI",

"CompanyName":"Alfreds Futterkiste",

"ContactName":"Maria Anders",
"ContactTitle":"Sales Representative",

"Orders": [

{

"odata.entryId":"http://DeltaService.svc/Orders(10643)",

"OrderDetails" : [

{"odata.entryId":"http://DeltaService.svc/Order_Details(OrderId=10643,ProductId=28)"},

{"odata.entryId":"http://DeltaService.svc/Order_Details(OrderId=10643,ProductId=39)"}

]

},

{

"odata.entryId":"http://DeltaService.svc/Orders(10692)",

"OrderDetails" : [

{"odata.entryId":"http://DeltaService.svc/Order_Details(OrderId=10692,ProductId=63)"}

]

}
]

}

Flattened Results

In OData V4, it's possible to request a flattened result. Flattened results are a special type of heterogeneous result in which the result represents a flatten graph (so the root is known). Clients may request, and know how to interpret, flattened results through a content-type parameter.

Content-Type for Flat Results

The Content-Type for a result representing a flattened hierarchy includes the odata.flat parameter; e.g., application/atom;odata.flat or application/json;odata.flat.

The client can specify the odata.flat parameter in the Accept header of the request.

The default shape for a hierarchical request, if not specified, is nested. Services MUST include the odata.flat parameter in the content-type header for flattened results.

The service MUST NOT respond to a data request with a flattened result unless the odata.flat parameter has been specified in the request. If the client only specifies odata.flat as an option in the Accept header, the service MUST return a flattened result or return 406 (Not Acceptable).

Specifying the Set for Related Entries

Each entry that is not from the root set (the target resource set of the request) MUST specify its set as a relative path from the service root.

OData version 4.0 clients MUST be prepared to deal with entries whose specified set is different than the root set.

Ordering of Flattened Results

Flattened responses are serialized in a depth-first manner; first leafs (grouped by navigation property), then parent: For example:

OrderDetails(OrderId=10643,ProductId=28)
OrderDetails(OrderId=10643,ProductId=39)
Orders(10643)
OrderDetails(OrderId=10692,ProductId=63)
Orders(10692)
Customers('ALFKI')

This ordering allows the hierarchy to be reconstructed from the flat results in a streaming fashion.

Next Links in Flattened Results

Flattened results MAY contain next links at any level in the hierarchy. If present, the next link for the root exists at the feed level; next links for related elements are in the navigation property of their parent.

$top and $skip for Flattened Results

Specifying $top or $skip in conjunction with a flattened result applies to the root entities; all expanded children of the selected root entities are also included in the result.

Flat Results in ATOM

Flat results in ATOM are represented as a single feed containing (potentially heterogeneous) atom entries, one for each entity in the result. For each expanded navigation property, the corresponding atom link entry contains the id(s) of the related entities. In a flat result, these related ids have no nested navigation properties.

The set for non-root entries MUST be specified through a metadata:set attribute on the <id> element.

Example: Flat ATOM Result

The example below shows a flat ATOM payload representing a Customer and two related Orders:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>

<feed xml:base="http://northwinddelta.cloudapp.net/DeltaService.svc/" xmlns:d="http://odata.org" xmlns:m="http://odata.org/metadata" xmlns="http://www.w3.org/2005/Atom">

<id>http://DeltaService.svc/Customers</id>

<link rel="self" title="Customers" href="Customers"/>

<entry>

<id m:set="$metadata/#Orders">http://services.odata.org/Northwind/Northwind.svc/Orders(10643)</id>

<link rel="edit" title="Order" href="Orders(10643)"/>

<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customer"
type="application/atom+xml;type=entry" title="Customer" href="Orders(10643)/Customer"/>

<category term="NorthwindModel.Order"

scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>

<content type="application/xml">

<m:properties>

<d:OrderID m:type="Edm.Int32">10643</d:OrderID>

<d:OrderDate m:type="Edm.DateTime">1997-08-25T00:00:00</d:OrderDate>

<d:RequiredDate m:type="Edm.DateTime">1997-09-22T00:00:00</d:RequiredDate>

<d:ShippedDate m:type="Edm.DateTime">1997-09-02T00:00:00</d:ShippedDate>

</m:properties>

</content>

</entry>

<entry>

<id m:set="$metadata/#Orders">http://services.odata.org/Northwind/Northwind.svc/Orders(10692)</id>

<link rel="edit" title="Order" href="Orders(10692)" />

<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customer"
type="application/atom+xml;type=entry" title="Customer" href="Orders(10692)/Customer"/>

<category term="NorthwindModel.Order"

scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />

<content type="application/xml">

<m:properties>

<d:OrderID m:type="Edm.Int32">10692</d:OrderID>

<d:OrderDate m:type="Edm.DateTime">1997-10-03T00:00:00</d:OrderDate>

<d:RequiredDate m:type="Edm.DateTime">1997-10-31T00:00:00</d:RequiredDate>

<d:ShippedDate m:type="Edm.DateTime">1997-10-13T00:00:00</d:ShippedDate>

</m:properties>

</content>

</entry>

<entry>

<id>http://DeltaService.svc/Customers('BOTTM')</id>

<title type="text"/>

<updated>2011-02-16T01:00:25Z</updated>

<author><name /></author>
<link rel="edit" title="Customer" href="Customers('BOTTM')"/>
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Orders"
type="application/atom+xml;type=feed" title="Orders" href="Customers('BOTTM')/Orders">

<m:inline>

<feed>

<m:entryId id="http://DeltaService.svc/Orders(10643)"/>

<m:entryId id="http://DeltaService.svc/Orders(10692)"/>

</feed>

</m:inline>

</link>

<category term="NorthwindModel.Customer"
scheme="http://odata.org/scheme" />
<content type="application/xml">
<m:properties>
<d:CustomerID>ALFKI</d:CustomerID>

<d:CompanyName>Alfreds Futterkiste</d:CompanyName>

<d:ContactName>Maria Anders</d:ContactName>

<d:ContactTitle>Sales Representative</d:ContactTitle>

</m:properties>
</content>
</entry>

</feed>

Flat Results in JSON

Flat results in JSON are represented as a single array containing one object for each entity in the result. Each property representing an expanded navigation property includes the ids of each related object. In a flat result, these related ids have no nested navigation properties.

MetadataUrl in JSON

The MetadataUrl for a flat result in JSON is the same as the metadata URL for a nested result, with /@Flat appended to the end of the URL.

The set for non-root entries MUST be specified through the odata.set annotation, and MUST appear before any property or property annotation.

Example: Flat JSON Result

The example below shows a flat JSON payload representing a Customer and two related Orders:

{

"odata.metadata":"http://DeltaService.svc/$metadata#Customers/@Flat",

"value":
[
{
"odata.set":"$metadata/#Orders",
"odata.id":"http://DeltaService.svc/Orders(10643)",
"OrderID":"10643",

"OrderDate":"1997-08-25T00:00:00",

"RequiredDate":"1997-09-22T00:00:00",

"ShippedDate":"1997-09-02T00:00:00"

},

{
"odata.set":"$metadata/#Orders",
"odata.id":"http://DeltaService.svc/Orders(10692)",
"OrderID":"10692",

"OrderDate":"1997-10-03T00:00:00",

"RequiredDate":"1997-10-31T00:00:00",

"ShippedDate":"1997-10-13T00:00:00"

},

{
"odata.id":"http://DeltaService.svc/Customers('ALFKI')",
"CustomerID":"ALFKI",

"CompanyName":"Alfreds Futterkiste",

"ContactName":"Maria Anders",
"ContactTitle":"Sales Representative",

"Orders":[

{

"odata.entryId":"http://DeltaService.svc/Orders(10643)"

},

{

"odata.entryId":"http://DeltaService.svc/Orders(10692)"

}
]

}

]

}


Notes/Issues:

-is set better represented in ATOM as an attribute of the <id> element or as a separate element?