Daten exponieren mit OData und Apache Olingo

Standards  –  0 Kommentare

Microsoft entwarf OData mit Blick auf den Austausch zwischen Softwaresystemen. Heute ist das Protokoll OASIS-Standard und kann bei der Bewältigung der Herausforderungen helfen, die heterogene Systeme mit sich bringen.

Folgendes Szenario sollte vielen Lesern bekannt vorkommen: Man hat unterschiedlichste isolierte Anwendungen, und plötzlich müssen sie in "die Service-Landschaft" des Unternehmens integriert werden. Und wie immer ist das im Projekt nicht nur schnell zu realisieren, sondern der Service muss einfach zu konsumieren sein und auf einem allgemein anerkannten Standard basieren.

An der Stelle setzt das Open Data Protocol (kurz OData) an, das einen Standard definiert, um Ressourcen sowohl anzubieten als auch zu konsumieren und so eine einheitliche Client-Server-Kommunikation zu realisieren. Hierzu sind die Ressourcen zunächst per Datenmodell (Entity Data Model (EDM), Metadata) zu definieren. Im Anschluss lassen sie sich per Query-Anfragen ($select, $top, $...) abfragen und mit CRUD-Operationen nach den REST-Prinzipien bearbeiten (Servicedocument).

Auf der OData-Webseite erklärt man die Funktion des Protokolls wie folgt:

"Das Open Data Protocol (OData) ermöglicht das Erstellen von REST-basierten Datendiensten, welches erlauben, Ressourcen, die über Uniform Resource Identifiers (URIs) identifiziert werden und in einem Datenmodell definiert sind, mittels der Verwendung von HTTP-Nachrichten, durch Web-Clienten zu veröffentlichen und zu bearbeiten."

Das Projekt Apache Olingo bietet hierzu eine Java-Bibliothek mit der Intention, sowohl einen OData-Service bereitzustellen als auch Unterstützung bei der Kommunikation mit OData-Services zu bieten.

In diesem ersten von zwei Artikeln zum Thema sind die Theorie hinter OData und wesentliche Punkte der Spezifikation Gegenstand der Betrachtung. Im zweiten wird anhand kurzer Code-Beispiele in Verbindung mit Olingo gezeigt, wie sich ein OData-Service aufsetzen und konsumieren lässt.

In der Theorie

OData wurde als Standard von Microsoft entworfen, um einen Datenaustausch zwischen Softwaresystemen zu ermöglichen. Die adressierten Schwerpunkte liegen hier bei der Server-zu-Server-Kommunikation und der Möglichkeit, einen angebotenen Service anhand seines Servicedokuments und des Datenmodells (EDM) nahezu automatisch einzubinden.

Die OData-Versionen 1.0, 2.0 und 3.0 sind per Microsoft Open Specification Promise freigegeben. Danach wurde der Standard an OASIS übergeben und dort weiterentwickelt. Die aktuelle Version 4.0 wurde am 26. Februar 2014 als erste unter der Obhut von OASIS spezifizierte Version freigegeben.

Im OData-Standard werden die Definition des Datenmodells per EDM (Entity Data Model), der Austausch der Daten per Services im ServiceDocument und das Datenformat (REST, AtomPub, JSON) festgelegt. Weiterhin sind Funktionen in Form der System Query Options, Service Operations (oder auch Function Imports) und Batch Processing definiert, um den Austausch von Daten zu optimieren sowie CRUD-Szenarien mit an SQL angelehnten Funktionen zu erweitern.

So dienen die System Query Options dazu, die angefragten Daten näher zu spezifizieren, um zum Beispiel die zurückgelieferte Datenmenge zu reduzieren, indem nur die notwendigen Daten abgefragt werden (etwa per $filter zum Filtern oder per $top, um nur die ersten n-Einträge zu bekommen). Es lässt sich zudem auch die Menge der Anfragen (Roundtrips) vermindern, indem man bei einer Anfrage auf einen Eintrag bestimmte referenzierte Einträge bei der Antwort direkt mitliefert (beispielsweise durch ein $expand). Folgende System Query Options sind in OData definiert: $expand, $filter, $orderby, $format, $skip, $top, $skiptoken, $inlinecount und $select.

Die Service Operations (oder auch Function Imports) kann man mit Stored Procedures in SQL vergleichen. Sie dienen der Definition von Funktionen, die sich per URI referenzieren lassen und somit auch zusätzliche Einstiegspunkte sind. Das Batch Processing dient dazu, mehrere Requests in einem einzigen Batch-Aufruf zusammenzufassen und an den Server zur Abarbeitung zu übermitteln.

Definition des Datenmodells

Durch die Notwendigkeit, ein Datenmodell (Entity Data Model (EDM)) für den angebotenen Service definieren zu müssen und es als Metadaten-Dokument bereitzustellen, kann sich ein Client alle notwendigen Informationen holen, um Daten mit dem Server auszutauschen. Das Metadata-Dokument muss per $metadata-Aufruf direkt unter der Service-URI abrufbar sein (etwa http://services.odata.org/OData/OData.svc/$metadata) und liegt im EDMX-Format vor (XML in Kombination mit der Conceptual Schema Definition Language).

Zusätzlich muss ein OData-Service noch ein sogenanntes Service Document bereitstellen (bspw. http://services.odata.org/OData/OData.svc), in dem die Einstiegspunkte zu den obersten Entitäten und spezielle Service Operations gelistet werden. Der Standard sieht für das Service Document das AtomPub- oder JSON-Format vor.

Das EDM definiert die Elemente Collection (Entity Set), Entry (Entity), Property, Complex Type, Link und Service Operation. Ein Entry entspricht einer Entität (zum Beispiel: ein Auto), das wiederum eine oder mehrere Properties (etwa Farbe, Räder und Hersteller) besitzt. Solch eine Property kann ein einfacher Typ, wie die Farbe als String, ein Complex Type, der wiederum aus Properties besteht, (zum Beispiel die Räder, die als eigene Properties ihre Größe und einen Typ besitzen) oder ein Link sein, der auf einen weiteren Entry verweist (etwa auf den Hersteller des Autos, der im EDM als eigener Entry definiert ist).

Da im Datenmodell auch die Verknüpfungen zwischen Datentypen (als Navigation) hinterlegt sind, kann sich ein Client anhand der Informationen sowohl alle Daten einer Entität als auch die aller mit ihr verknüpften Entitäten holen. Für Entitäten (Entry) lassen sich dann Collections (Entity Sets) definieren, um festzulegen, dass sie per Feed abgerufen werden können. Zudem bedeutet das, dass sie als Einstiegspunkte im Service Document auftauchen.

Die Service Operations (oder Function Imports) sind eine Erweiterung der Einstiegspunkte, um spezielle Funktionen des Service zu definieren und anzubieten. Als Beispiel könnte man eine Service Operation definieren, die zwei Parameter Farbe und Hersteller erwartet und als Ergebnis alle Autos liefert, die diesen Kriterien entsprechen.

Definition eines Service

Eine Service-Definition erledigt der Nutzer bereits implizit bei der Definition des Datenmodells (EDM), da das Service Document alle Collections (Entity Sets) und Service Operations enthält.

Durch das Umsetzen der REST-Prinzipien in OData sind alle Einstiegspunkte, die sich auf Collections beziehen, URIs, die bei einem GET einen Feed gemäß dem AtomPub-Format liefern. Jeder Eintrag im Feed entspricht wiederum einer Entität, die durch eine eindeutige URI referenziert wird. In der Praxis sieht es so aus, dass sich ein EntitySet per URI ../OData.svc/Cars abrufen lässt. Der Feed enthält dann mehrere Entitäten mit deren URI ../OData.svc/Cars('1'). Letztere lässt sich direkt aufrufen, um die Daten für genau diese Entität abzufragen. Weiterhin können Entwickler einzelne Properties einer Entity ebenso per URI abrufen, als Beispiel würde ../OData.svc/Cars('1')/color die Farbe des Autos liefern. Entsprechend ist nicht nur jede Entität, sondern auch jeder Datensatz eindeutig über eine URI referenzierbar.

CRUD

Alle Beziehungen einer Entity zu anderen werden ebenso als URIs speziell über die Navigation Property gelistet. Dadurch ist es möglich, von einer Entität direkt auf von ihr referenzierte Entitäten zu navigieren.

Der OData-Standard unterstützt AtomPub und JSON als Austauschformat der Daten. Als Standard ist AtomPub definiert (Accept-Header: application/atom+xml). JSON lässt sich mit speziellen Aufrufparametern beim Service anfordern (entweder per Accept-Header: application/json oder $format=json).

Beim Anlegen und/oder Modifizieren von Daten gilt AtomPub (Content-Type: application/atom+xml) als Standard, JSON lässt sich zudem angeben (Content-Type: application/json).

Basic CRUD

Für das Anlegen einer Entität wird ein HTTP-POST mit entsprechendem Content im HTTP BODY und korrekt gesetztem Content-Type HTTP Header auf die URI des EntitySet durchgeführt (REST-Prinzipien). Im aktuellen Beispiel wäre das ein POST auf die URI ../OData.svc/Cars mit Content-Type=application/atom+xml und folgendem Content im Body:

HTTP-POST Request Body:

<?xml version='1.0' encoding='utf-8'?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">
<content type="application/xml">
<m:properties>
<d:Model>CarModel</d:Model>
<d:Price>47110</d:Price>
<d:ModelYear>2014</d:ModelYear>
</m:properties>
</content>
</entry>

Der Aufruf liefert (entsprechend dem gesetzten Accept Header) die Daten der angelegten Entität zurück. Anhand der in dem Beispielservice angelegten Entität kann man sehen, dass es möglich ist, bestimmte Property-Felder beim Anlegen leer zu lassen, die im Anschluss vom Service nach den im EDM festgelegten Standardwerten befüllt werden. Erwähnenswert ist zudem, dass das Key-Property-Feld, im Beispiel die Id, beim POST nicht mitgeschickt wurde. Es liegt im Ermessen des Service, wie damit umgegangen wird.

Das Standardvorgehen für OData-Services ist, dass der Service die Key-Property-Felder beim Anlegen einer Entität entsprechen befüllt. Hierbei ist es sogar so, dass mitgeschickte Key-Property-Werte vom Service ignoriert werden können und dieser die Key-Property-Werte generiert. In der zurückgelieferten Entität sind die entsprechenden Felder dann so befüllt, wie sie vom Service verarbeitet und gegebenenfalls persistiert wurden.

HTTP-POST Response Body:

<?xml version='1.0' encoding='utf-8'?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">
<id>http://localhost:8080/MyFormula.svc/Cars('6')</id>
<title type="text">Cars</title>
<updated>2014-01-28T08:17:23.363+01:00</updated>
<category term="MyFormula.Car" scheme="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/scheme"/>
<link href="Cars('6')" rel="edit" title="Car"/>
<link href="Cars('6')/Manufacturer"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related↩
/Manufacturer"
title="Manufacturer" type="application/atom+xml;type=entry"/>
<link href="Cars('6')/Driver"
rel="http://schemas.microsoft.com/ado/2007/08/dataservice/related↩
/Driver"
title="Driver" type="application/atom+xml;type=entry"/>
<content type="application/xml">
<m:properties>
<d:Id>6</d:Id>
<d:Model>CarModel</d:Model>
<d:Price>47110.0</d:Price>
<d:ModelYear>2014</d:ModelYear>
<d:Updated m:null="true"/>
</m:properties>
</content>
</entry>

Lesend zugreifen

Für das Lesen von Entitäten gibt es zwei wesentliche Möglichkeiten. Eine angelegte Entität lässt sich durch ihre eindeutige URI direkt referenzieren und somit lesen. Alternativ kann der Feed für eine Entität über ein zuvor per EDM definiertes EntitySet (Collection) referenziert werden. In der Folge lassen sich alle bekannten Entitäten lesen.

Technisch bedeutet das Lesen ein HTTP-GET auf die URI. Durch das Setzen des Accept HTTP Header lässt sich das gelieferte Format beeinflussen. Im aktuellen Beispiel wäre das ein GET auf die URI ../OData.svc/Cars mit Accept: application/atom+xml um alle Autos zu bekommen, was im in Listing gezeigten Response Body resultiert. Ein GET auf die URI eines Autos ../OData.svc /Cars('1') mit Accept: application/atom+xml, um das Auto mit dem Key von 1 zu bekommen, erzeugt hingegen:

HTTP-GET Response Body:

<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">
<id>http://localhost:8080/MyFormula.svc/Cars('1')</id>
<title type="text">Cars</title>
<updated>2014-01-30T10:15:36.237+01:00</updated>
<category term="MyFormula.Car" scheme="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/scheme"/>
<link href="Cars('1')" rel="edit" title="Car"/>
<link href="Cars('1')/Manufacturer" rel="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/related/Manufacturer" title="Manufacturer"
type="application/atom+xml;type=entry"/>
<link href="Cars('1')/Driver" rel="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/related/Driver" title="Driver"
type="application/atom+xml;type=entry"/>
<content type="application/xml">
<m:properties>
<d:Id>1</d:Id>
<d:Model>F1 W02</d:Model>
<d:Price>167189.0</d:Price>
<d:ModelYear>2011</d:ModelYear>
<d:Updated>2014-01-30T09:13:34.847</d:Updated>
</m:properties>
</content>
</entry>

Darüber hinaus lassen sich Property-Felder von Entitäten direkt referenzieren und somit lesen (entsprechend den REST-Prinzpien).

Daten aktualisieren

Für ein Update der Daten einer Entität gibt es unterschiedliche Möglichkeiten. Die wichtigsten sind HTTP-PUT und HTTP-PATCH / HTTP-MERGE auf die eindeutige URI (etwa ../OData.svc/Car('1')) einer Entität, mit entsprechendem Content im HTTP BODY und korrekt gesetztem Content-Type HTTP Header (REST-Prinzipien).

Bei einem HTTP-PUT auf die eindeutige URI einer Entität werden alle im Request Body gesendeten Property-Felder gelesen und entsprechend bei der Entität aktualisiert. Nicht im Request-Body mitgeschickte Felder interpretiert das System als NULL und löscht sie bei der entsprechenden Entität (beziehungsweise setzt es sie auf m:null=true).

HTTP und Abfragen

Wendet man HTTP-PATCH/HTTP-MERGE auf die eindeutige URI einer Entität an, werden alle im Request Body geschickten Property-Felder gelesen und entsprechend bei der Entität aktualisiert. Im Gegensatz zum HTTP-PUT interpretiert das System nicht im Request-Body mitgeschickte Property-Felder jedoch nicht als NULL, sondern ignoriert sie beim Update einfach.

Die Key-Property-Felder sind im Request Body nicht mitzuschicken, da sie bereits in der eindeutigen URI der Entität vorhanden sind. Entsprechend lassen sie sich auch nicht durch ein Update verändern. Werden die Key-Property-Felder im Request-Body mitgeschickt, kann der OData-Service entscheiden, ob er mit einem Fehler reagiert oder sie beispielsweise einfach ignoriert.

Sowohl beim HTTP-PUT als auch beim HTTP-PATCH / HTTP-MERGE ist bei einem erfolgreichem Update das Ergebnis ein HTTP Response mit einem Status von 204: No Content.

Beispiele für HTTP-PUT/PATCH/MERGE

Im aktuellen Beispiel wäre das ein PUT auf die URI ../OData.svc/Car('1') mit Content-Type=application/atom+xml und folgendem Content im HTTP BODY, um den Preis (d:Price) zu aktualisieren.

<?xml version='1.0' encoding='utf-8'?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m=↩
"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">
<content type="application/xml">
<m:properties>
<d:Model>F1 W02</d:Model>
<d:Price>47110.0</d:Price>
<d:ModelYear>2011</d:ModelYear>
<d:Updated>2014-01-30T09:13:34.847</d:Updated>
</m:properties>
</content>
</entry>

Wie man sieht, sind bei einem HTTP-PUT auch die übrigen Felder (d:Model, d:ModelYear, i]d:Updated[/i]) mit ihren alten Werten mitzuschicken, um nicht auf NULL gesetzt zu werden.

Um sich das nicht notwendige Mitsenden der Felder, deren Wert sich nicht ändert (d:Model, d:ModelYear, d:Updated), zu ersparen, lässt sich ein HTTP-PATCH- / HTTP-MERGE-Requests verwenden. Die URI bleibt ebenso identisch ../OData.svc/Car('1') wie der Content-Type=application/atom+xml. Jedoch reicht jetzt folgender Content im HTTP BODY, um nur den Preis (d:Price) zu aktualisieren:

<?xml version='1.0' encoding='utf-8'?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:m=↩
"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">
<content type="application/xml">
<m:properties>
<d:Price>47110.0</d:Price>
</m:properties>
</content>
</entry>

Beide Beispiele resultieren darin, dass der Preis des Autos aktualisiert wurde. Der einzige Unterschied ist, dass im Request Body weniger Daten zu schicken sind.

Für das Löschen einer Entität ist ein HTTP-DELETE-Aufruf auf die eindeutige URI der Entität durchzuführen. Im aktuellen Beispiel wäre das ein DELETE auf die URI ../OData.svc/Cars('1'), um das Auto mit dem Key von 1 zu löschen, was in einem HTTP-Response-Status von 204: No Content resultiert.

Ein direkt danach ausgeführter lesender Zugriff auf die Entität (per HTTP-GET auf die URI ../OData.svc/Cars('1')) würde dann ein in einem HTTP-Response-Status von 404: Not Found resultieren.

Abfragen mit System

In OData gibt es sogenannte System Query Options, die dazu dienen, Abfragen näher zu spezifizieren. Dazu sind folgende HTTP-Query-Parameter mit vorgestelltem $ als Schlüsselworte reserviert: $expand, $filter, $orderby, $format, $skip, $top, $skiptoken, $inlinecount und $select.

So kann man mit $filter nur die für eine aktuell relevanten Entitäten abfragen. Als Beispiel würde ein Lesen per GET auf ../OData.svc/Cars mit gesetztem $filter=ModelYear eq 2013 nur die Autos aus dem Jahr 2013 liefern.

Mit $expand erreicht man, dass beim Lesen einer Entität noch weitere Daten, die sie referenziert, mitgeschickt werden. Als Beispiel würde ein Lesen per GET auf ../OData.svc/Manufacturers mit gesetztem $expand=Cars nicht nur die Daten des Hersteller, sondern mit der selben Response auch die Daten aller dem Hersteller zugeordneten Fahrzeuge liefern.

Per Service Operations (oder auch Function Imports) kann ein OData-Service zusätzliche Einstiegspunkte anbieten, die sich mit HTTP-GET und HTTP-POST in Verbindung mit Parametern aufrufen lassen. Ein einfaches Beispiel wäre eine Service Operation, um alle Hersteller zu bekommen, die Autos in einer bestimmten Farbe anbieten. Ein GET auf ../OData.svc/GetManufacturerWithCar?color='yellow' würde im Anschluss eine entsprechende Liste von Autos liefern.

Bei Service Operations besteht der Unterschied zwischen HTTP-GET und HTTP-POST lediglich in der Definition, dass nur Service-Operationen ein HTTP-GET ohne Nebeneffekte erlauben sollten. Werden durch die definierten Service Operations Werte verändert, so soll dieser nur per HTTP-POST erreichbar sein und einen Aufruf per HTTP-GET ablehnen. Den Body des HTTP-POST kann eine Service Operation nicht verarbeiten.

Durch das Batch Processing kann man mehrere OData-Operationen in einen einzigen HTTP Request verpacken und zum Abarbeiten an den Server übertragen. Der entsprechende Batch Request besteht aus mehreren lesenden (GET) und/oder schreibenden Operationen (Changesets), die in geordneter Reihenfolge auf dem Server ausgeführt werden. Die schreibenden Operationen wiederum können aus einem oder mehreren POST/PUT/DELETE HTTP Requests bestehen, jedoch kein GET und keine weiteren Changesets enthalten. Beispiele mit komplettem Content gibt es in der OData-Dokumentation.

Fazit

OData definiert einen Standard, mit dem sich Ressourcen anbieten und konsumieren lassen. Ziel seiner Entwickler war es, eine einheitliche Client-Server-Kommunikation zu realisieren. Eine besondere Rolle nimmt das Entity Data Model ein, in dem die Einstiegspunkte zu den oberen Entitäten und spezielle Service Operations gelistet werden. Mit System Query Options lassen sich Abfragen näher spezifizieren und auch das stapelweise Verarbeiten von OData-Operationen ist via Batch Request möglich.

In der Praxis wird man bei der Implementierung eines OData-Service schnell feststellen, dass es einige generische Teile gibt, die unabhängig von den Daten und der Logik der eigentlichen Applikation sind und sich nur auf die OData-Spezifikation beziehen.

Entsprechend lohnt es sich, für generische Teile bereits vorhandene Bibliotheken zu nutzen und den OData-Service darauf aufzubauen. Im Java-Umfeld gibt es hier das Olingo-Projekt, das solch eine Bibliothek bereitstellt. Sie wird im zweiten Artikel verwendet, um einen OData- Service beispielhaft zu realisieren. (jul)

Michael Bolz
arbeitet seit mehr als einem Jahr im OData-Umfeld und war in dieser Zeit unter anderem dabei, die OData-Spezifikation (in Version 2) im Apache Olingo Projekt als Open-Source-Implementierung zu realisieren.

Listing

Listing 1: HTTP-GET Response Body

<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xml:base="http://localhost:8080/MyFormula.svc/">

<id>http://localhost:8080/MyFormula.svc/Cars</id>
<title type="text">Cars</title>
<updated>2014-01-30T10:13:34.923+01:00</updated>
<author>
<name/>
</author>
<link href="Cars" rel="self" title="Cars"/>
<entry>
<id>http://localhost:8080/MyFormula.svc/Cars('1')</id>
<title type="text">Cars</title>
<updated>2014-01-30T10:13:34.925+01:00</updated>
<category term="MyFormula.Car"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices↩
/scheme"/>
<link href="Cars('1')" rel="edit" title="Car"/>
<link href="Cars('1')/Manufacturer"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related↩
/Manufacturer"
title="Manufacturer" type="application/atom+xml;type=entry"/>
<link href="Cars('1')/Driver" rel="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/related/Driver"
title="Driver" type="application/atom+xml;type=entry"/>
<content type="application/xml">
<m:properties>
<d:Id>1</d:Id>
<d:Model>F1 W02</d:Model>
<d:Price>167189.0</d:Price>
<d:ModelYear>2011</d:ModelYear>
<d:Updated>2014-01-30T09:13:34.847</d:Updated>
</m:properties>
</content>
</entry>
<entry>
...
</entry>
...
</entry>
<entry>
<id>http://localhost:8080/MyFormula.svc/Cars('4')</id>
<title type="text">Cars</title>
<updated>2014-01-30T10:13:34.927+01:00</updated>
<category term="MyFormula.Car"
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices
/scheme"/>
<link href="Cars('4')" rel="edit" title="Car"/>
<link href="Cars('4')/Manufacturer"
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related↩
/Manufacturer"
title="Manufacturer" type="application/atom+xml;type=entry"/>
<link href="Cars('4')/Driver" rel="http://schemas.microsoft.com↩
/ado/2007/08/dataservices/related/Driver" title="Driver"
type="application/atom+xml;type=entry"/>
<content type="application/xml">
<m:properties>
<d:Id>4</d:Id>
<d:Model>FF2014</d:Model>
<d:Price>299189.11</d:Price>
<d:ModelYear>2014</d:ModelYear>
<d:Updated>2014-01-30T09:13:34.847</d:Updated>
</m:properties>
</content>
</entry>
</feed>