4.4 Relationship Definitions
In an SData schema, relationships are expressed through property definitions. The presence of an sme:relationship attribute differentiates relationship definitions from normal property definitions. Here are two examples from our salesOrder complex type definition:
<xs:element name="orderLines" type="tns:salesOrderLine--list" minOccurs="0" sme:relationship="child" sme:isCollection="true" sme:label="Order Lines" sme:canGet="true" sme:canPost="true" /> <xs:element name="contact" type="tns:contact--type" minOccurs="0" sme:relationship="reference" sme:label="Contact" sme:canGet="true" sme:isMandatory="true" />
The first one is an orderLines property, which links a sale order resource to its collection of lines. The second one is a contact property which links a sales order resource to a contact resource.
If a relationship can be traversed in both directions, it must be represented by two property definitions. For example, the orderLines property defined in the salesOrder resource kind has a counterpart in the salesOrderLine resource kind: a salesOrder property that links an order line to its parent sales order. The following schema fragment shows how the complete bidirectional relationship is expressed:
<xs:complexType name="salesOrder--type"> <xs:all> <!-- other properties (skipped for clarity) --> <xs:element sme:relationship="child" sme:isCollection="true" sme:label="Order Lines" minOccurs="0" name="orderLines" type="tns:salesOrderLine--list" /> </xs:all> </xs:complexType> <xs:complexType name="salesOrderLine--type"> <xs:all> <!-- other properties (skipped for clarity) --> <xs:element sme:relationship="parent" sme:label="Order" minOccurs="0" name="order" type="tns:salesOrder--type" /> </xs:all> </xs:complexType>
The sme:relationship attribute classifies the relationship in one of the following categories:
|parent||salesOrderLine.salesOrder (parent sales order for an order line)||Relationship to a parent resource.|
|child||salesOrder.orderLines (lines contained in a sales order)||Relationship to a child resource.|
|reference||salesOrder.contact (contact linked to a sales order) salesOrderLine.product (product linked to an order line)||Reference to a shared resource. Many sales orders sharing the same contact resource.|
|association||customer.salesOrders (all the sales orders of a customer)||Arbitrary relationship that does not fall into one of the above categories.|
The following rules apply to these relationship categories:
- A parent relationship is always the reverse of a child relationship.
- A parent relationship property always has its sme:isCollection attribute set to false (but a child relationship may have sme:isCollection set to true).
The graph obtained by traversing child relationships should always be a tree. Cycles are not permitted: a resource cannot be a descendant of itself.
- A reference relationship property always has its sme:isCollection attribute set to false.
This categorization impacts the behavior of some SData artifacts. The main ones are:
- include=$children query parameter: resources that are referenced via “child” relationships will be recursively included in the response payload when this query parameter is set in the URL (see Query Parameters section).
- operations allowed on property URLs. POST, PUT and DELETE are only allowed on child resources (see Resource Property URL section).
- synchronization: child resources are synchronized together with their parent, they are not synchronized separately (see Relationship Handling in Synchronization).
So, relationships should be categorized as parent/child only when the child resource cannot exist without its parent and when there is some kind of container/component relationship between the two. At the database level, children records will typically be managed with a cascade delete rule.
For example, it seems logical to treat order lines as children of sales order resources. On the other hand, sales orders should not be considered to be children of a trading account resource. The relationship from a trading account to its sales orders should be categorized as an association. The opposite relationship, from a sales order to its trading account, should be categorized as a reference.
The sme:isCollection attribute controls whether the property refers to a collection of resources or a single resource.
In the example, sme:isCollection is true on the orderLines property because a sales order has a collection of order lines. On the other hand, it is false (its default value) on the contact property because a sales order references a single contact.
sme:isCollection is most meaningful on child relationships. It MUST always be false (its default value) on parent or reference relationships, and true on association relationships.
The standard XSD type attribute references a complex type described in the schema.
If the sme:isCollection attribute is absent or false (for example, salesOrderLine.salesOrder, salesOrder.contact, salesOrderLine.product), the type MUST be a complex type associated to a resource (salesOrder–type, contact–type, product–type respectively).
If the sme:isCollection attribute is true (for example, salesOrder.orderLines), the type MUST be a “list” type (salesOrderLine–list).
The sme:isMandatory attribute indicates if the property element is mandatory to successfully create a resource (POST).
sme:isMandatory SHOULD NOT be set on properties that are set by the provider when a resource is created, only on properties that MUST be set by the consumer before POSTing to the provider.
The sme:copiedFrom attribute connects a property of a resource to a property of a related resource so that when the related resource changes the resource property is updated too. Its primary use is with reference relationships. It is applied to properties that are involved in foreign keys or that hold denormalized data (find out more about denormalized data on Wikipedia). This is best explained with an example:
<xs:complexType name="salesOrder--type"> <xs:all> <!-- other properties (skipped for clarity) --> <xs:element name="contactID" type="xs:integer" minOccurs="0" sme:copiedFrom="contact/contactID"/> <xs:element name="contact" type="tns:contact--type" minOccurs="0" sme:relationship="reference" sme:label="Contact" /> </xs:all> </xs:complexType> <xs:complexType name="contact--type"> <xs:all> <xs:element name="contactID" type="xs:integer" minOccurs="0" sme:label="ID" /> <!-- other properties (skipped for clarity) --> </xs:all> </xs:complexType>
The sme:copiedFrom attribute is applied to the contactID property of sales orders. Its value is an XPath expression that designates another element of the salesOrder XML payload (the <contactID> element under the <contact> element). Meaning contactID must be updated from contact/contactID whenever the contact reference changes.
If some other contact property (for example lastName) had been denormalized in the sales order (for example as contactLastName), the sales order property definition(contactLastName) would also carry an sme:copiedFrom attribute (with contact/lastName as value).
This attribute is used by generic UI clients, typically to manage combo boxes in edit forms. In the example, the sales order edit form contains a combo box to select a contact. When the user selects a different contact, the form manager identifies which properties of the sales order need updating by scanning the sme:copiedFrom attributes of the sales order property definitions.
This attribute is deprecated because primary key values are now passed via sdata:key attributes rather than via element values.
SData supports HTTP operations (GET, POST, PUT and DELETE) on property URLs that represent relationships between resources. However, all operations are not allowed on all relationship properties. The canGet, canPost, canPut and canDelete attributes SHOULD be set on relationship properties for all allowed operations. See Resource Property URL section.
If the relationship is flagged with sme:isCollection=”true”, the property URL returns an Atom feed. sme:canPageXxx attributes SHOULD be included in the property definition if the provider supports paging on the returned feed. See Query Paging section. For example:
<xs:element name="orderLines" type="tns:salesOrderLine--list" minOccurs="0" sme:relationship="child" sme:isCollection="true" sme:label="Order Lines" sme:canPageNext="true" sme:canPagePrevious="true" sme:canPageIndex="true" sme:canGet="true" sme:canPost="true" />
In this example, the provider supports all available paging modes on the orderLines collection. If these attributes had been omitted, the consumer could assume that the provider does not support paging on this collection and that the orderLines feed will always contain all lines.
The sme:canSort, sme:canGroup and sme:canFilter attributes are also allowed on relationship properties, with their usual meaning.