Relationship links

Problem StatementOData (the protocol used by WCF Data Services) enables you to address the relationships between Entries. This functionality is required to be able to create or change a relationship between two instances, such as an Order_Detail that is related to a given Order. Currently the OData protocol requires clients and servers to agree up front on how to address relationships. For example, most OData implementations today follow the optional URL conventions for addressing links stated in the main OData spec. This convention states that a “$links” URI path segment be used to distinguish the relationship between Entries as opposed to an entry itself. For example, the following URI addresses the relationship between Order 1 and one or more OrderDetail Entries: https://myserver/myODataService/Orders(1)/$links/OrderDetails . Currently many of the OData client libraries rely on this $links convention when manipulating relationships.

In an effort to make the protocol (OData) used by odata services more “hypermedia friendly” and reduce the coupling between clients and servers (by allowing a client to be fully response payload driven) we would like to remove the need for a URL convention and have the server state (in the response payload) the URIs which represent the relationships for the Entry represented in a given response.

Design (state: draft)

The OData protocol already has the constructs necessary to express navigation property URIs in the form of standard <atom:link> elements. For example, the following is the atom representation for an Order Entry which has a navigation property Order_Details expressed as an <atom:link>.

<entry>  <category term="SampleModel.Order"              scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>   <id>https://host/service.svc/Orders(1)</id>  <title type="text" />  <updated>2008-03-30T21:52:45Z</updated>  <author>    <name />  </author>  <link rel="edit" title="Orders" href="Orders(1)" mce_href="Orders(1)" />   <link rel=" https://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"     type="application/atom+xml;type=feed" title="Order_Details" href=" https://host/service.svc/Orders(1)/Order_Details" />   <content type="application/xml">    <m:properties>      <d:OrderID m:type="Edm.Int32">1</d:OrderID>      <d:ShippedDate m:type="Edm.DateTime">1997-08-25T00:00:00</d:ShippedDate>    </m:properties>  </content></entry>

 

Following this pattern, the URI needed to address the relationship between entities can also be expressed as a link element in the following form:
<link  rel="https://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/<relationshipPropertyName>"   type="application/ xml” Title=”< relationshipPropertyName >"  ” href=" relatedLinksURI” />

Where:
- relationshipPropertyName is a navigation property name as defined in CSDL associated with the datat service.
- relatedLinksURI is URI which identifies the relationship between the Entry represented by the parent <entry> and another Entry (or group of Entries) as identified by the navigation property

The example below shows an Order entry with a link element (as described above) that represents the orders’ relationship with OrderDetails as a response to the following GET query ”https://host/service.svc/Orders(1)”.

<entry>  <category term="SampleModel.Order"    scheme="https://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>  <id>https://host/service.svc/Orders(1)</id>  <title type="text" />  <updated>2008-03-30T21:52:45Z</updated>  <author>    <name />  </author>  <link rel="edit" title="Orders" href="Orders(1)" mce_href="Orders(1)" />    <link    rel="https://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"    type="application/atom+xml;type=feed" title="Order_Details"    href=" https://host/service.svc/Orders(1)/Order_Details" />   <link rel=" https://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Order_Details"     type="application/xml” Title=”Order_Details”" href=" https://host2/Orders(1)/ $links/Order_Details”/>   <content type="application/xml">    <m:properties>      <d:OrderID m:type="Edm.Int32">1</d:OrderID>      <d:ShippedDate m:type="Edm.DateTime">1997-08-25T00:00:00</d:ShippedDate>    </m:properties>  </content></entry>

 

For JSON we would use “associationuri” to hold the relationship URI. As shown in the example below the associationuri would be a sibling of the uri property on a navigation property.

DataServiceVersion: 3.0;{  d : {    __metadata: {      uri: https://host/service.svc/Orders(1),      type: " SampleModel.Order",    },    OrderId: 1,    ShippedDate: "1997-08-25T00:00:00",    Order_Details: {      __deferred: {           uri: “https://host/service.svc/Orders(1)/Order_Details/” ,           associationuri: “https://host2/Orders(1)/$links/Order_Details”       }    }  }}

In the case of expanded relationships (using data services $expand operator) the associationuri would be placed before the “results” of the expanded related entities. For example the following GET query request “https://host/service.svc /Orders(1)?$expand=Order_Details&$format=json” would produce the following server payload:

DataServiceVersion: 3.0;{  d : {  __metadata: {      uri: https://host/service.svc/Orders(1),      type: " SampleModel.Order",    },  OrderId: 1,  ShippedDate: "1997-08-25T00:00:00",  Order_Details:{    __linkInfo : { associationuri: “ https://host2/Orders(1)/ $links/Order_Details” } ,    results:[    {      __metadata: {        uri: https://host/service.svc/Order_Details(OrderID=10643,ProductID=28),        type: "SampleModel.Order_Details"        },    OrderID: 1,    ProductID: 28,    UnitPrice: "45.6000",    Quantity: 15,    Discount: 0.25,    Orders: {      __deferred: {        uri: “https://host/service.svc/Order_Details(OrderID=1,ProductID=28)/Orders” ,        associationuri: “ https://host2/Order_Details(OrderID=1,ProductID=28)/ $links/Orders ”      }   },   Products: {    __deferred: {      uri: “https://host/service.svc/Order_Details(OrderID=10643,ProductID=28)/Products”,      associationuri: “ https://host2/Order_Details(OrderID=1,ProductID=28)/ $links/Products ” }}}]}}}

Server Rules:

- The server MAY return URI that represents the relationship between two Entries.

Client Rules:

- Client MUST use relationship URIs obtained from the server payload when addressing a relationship between two Entries, if present in the server response

- If the relationship URI is not present in the server response the client runtime MAY choose to use convention, for URI to construction, to address the relationship.

Backwards Compatibility:

Existing WCF Data Services clients that rely on convention, for URI construction, to address the relationship between two Entries will continue to work as long as the server supports the convention.

We look forward to hearing what you think of this approach...

Ahmed Moustafa

Program Manager, WCF Data Services

This post is part of the transparent design exercise in the Astoria Team. To understand how it works and how your feedback will be used please look at this post.

Technorati Tags: Design notes

Comments

  • Anonymous
    April 08, 2010
    I like the idea of encoding the relationship information w/o resorting to URI convention. But the suggested use of the rel and title attributes is, I suspect, going to cause trouble for state machine clients.I'd like to see something like:<link rel="relationship" href="{uri-to-details}" />IOW, offer a <link /> that points the user agent to a resource that contains the details you are trying to encode into your REL and TITLE attributes.This will give the most flexibility, prevent OData from encoding resource-specific information into relation links, and prevent OData from overriding the use of TITLE.
  • Anonymous
    April 15, 2010
    I am just looking at the Data Service Provider toolkit and I notice several things.1.) You need a huge amount of code to expose the most simple classes likepublic Class B{  public string NoIdProperty {get;set;}}public Class A{  public B MyB { get;set; }  public int MyIDProperty { get; set; }}2.) You do really messy things like in GetQueryRootForResourceSet(ResourceSet) function3.) The whole System.Data.Services.Providers Namespace is as good as undocumented4.) The whole namespace thing smells like entity framework to me5.) The whole error reporting/debugging is not even close to real life requirements. I definately need more details that just "Something blew up somewhere".I really love the idea of a WCF Service that exposes objects and I love the whole basic concept behind data services.Maybe I am just too stupid, but so far for me it looks the current ADO .NET Data Service implementation will probably never match my expectations.Maybe you should think about dumping the current implementation and start a rewrite that is focused on exposing objects (not just entity framework) from the scratch. Trying to fix the current code base seems to be a big waste of your and my time. But at least you should do your homework in regards to documentation before thinking about extensions.Cheers,Tobias
  • Anonymous
    April 19, 2010
    The comment has been removed