Problem Statement

OData (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: http://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="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>

  <id>http://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="
http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
    type="application/atom+xml;type=feed" title="Order_Details"
    href="
http://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="http://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 ”http://host/service.svc/Orders(1)”.

<entry>
  <category term="SampleModel.Order"
    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
  <id>http://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="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details"
    type="application/atom+xml;type=feed" title="Order_Details"
    href=" http://host/service.svc/Orders(1)/Order_Details" />

  <link                rel="http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks/Order_Details"
    type="application/xml” Title=”Order_Details”"
    href="
http://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: http://host/service.svc/Orders(1),
      type: " SampleModel.Order",
    },
    OrderId: 1,
    ShippedDate: "1997-08-25T00:00:00",
    Order_Details: {
      __deferred: {
           uri: “http://host/service.svc/Orders(1)/Order_Details/” ,
           associationuri: “http://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 “http://host/service.svc /Orders(1)?$expand=Order_Details&$format=json” would produce the following server payload:

DataServiceVersion: 3.0;
{
  d : {
  __metadata: {
      uri: http://host/service.svc/Orders(1),
      type: " SampleModel.Order",
    },
  OrderId: 1,
  ShippedDate: "1997-08-25T00:00:00",
  Order_Details:{
    __linkInfo : {
      associationuri: “
http://host2/Orders(1)/$links/Order_Details”
    }
,
    results:[
    {
      __metadata: {
        uri: http://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: “http://host/service.svc/Order_Details(OrderID=1,ProductID=28)/Orders” ,
        associationuri: “http://host2/Order_Details(OrderID=1,ProductID=28)/$links/Orders
      }
   },
   Products: {
    __deferred: {
      uri: “http://host/service.svc/Order_Details(OrderID=10643,ProductID=28)/Products”,
      associationuri: “http://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: