OData Model – Wie kommen die Daten in meine Anwendung?

Das OData Model besitzt laut API-Dokumentation einige Funktionen die suggerieren, dass ich von diesem Daten bekommen könnte. Binding, ja, da sind die Daten dann da, so irgendwie. Und wenn ich mein OData Model im Controller in der Hand habe, dann bekomme ich die Daten auch. Also zumindest sollte ich das in der Theorie

Aber wann kommen eigentlich wo die Daten her? Wann habe ich die Daten auch praktisch in der Hand und nicht nur theoretisch? Während der Entwicklung von meinen Anwendungen war mir das nicht immer klar. Deshalb folgt hier ein kurzer Versuch der Erklärung mit der Hoffnung, einige Unklarheiten zu beseitigen.

Daten Laden über ein Binding im XML

Der Standardfall ist das Laden der Daten über ein Binding direkt im XML-Code. Entscheidend sind hierbei immer zwei Sachen:

  • Das Model, dass ich auf mein Control gesetzt habe
  • Der Path, der die genaue Entität bzw. Entitäten benennt, die aus dem Model gelesen werden sollen

Model und Path zusammen bilden den Binding-Context. Aus diesem kann ich, sofern die Daten geladen worden sind, dann auch immer das exakte Objekt auslesen.
Ein wichtiges Unterscheidungs-Kriterium beim Binding-Context ist, ob es sich um ein Property- oder um ein Aggregation-Binding handelt:
Bei einem Property-Binding binde ich eine bestimmte Entität, wohingegen ich bei einem Aggregation-Binding ein Array an Entitäten binde (selbst wenn in dem Array nur eine Entität vorhanden ist). Der Unterschied zwischen diesen beiden Bindings ist auch sehr gut am jeweiligen Binding-Context zu sehen

Property Binding
{oModel: constructor, sPath: "/Debitor('DE12345678')"}

Aggregation Binding
{oModel: constructor, sPath: "/Debitor"}

Bei einem Property-Binding ist immer eine Entität mit samt einer Id gebunden, bei einem Aggregation-Binding nicht.

Wann funktioniert oModel.getObject(…)?

Ein OData Model besitzt laut API-Dokumentation die Methode getObject. Diese besagt, dass damit ein bestimmter Datensatz abgerufen werden kann. Allerdings liefert die Methode getObject nicht immer das gewünschte Ergebnis. In der Erwartung einen bestimmten Datensatz zu bekommen wurde ich des Öfteren enttäuscht. Aber woran liegt das?
Rein syntaktisch ist der Aufruf der Methode recht klar. Die gewünschte Entität wird mit ihrer Id wie folgt aufgerufen:

oModel.getObject("/DebitorSet('12345')");

So weit, so gut. Nun habe ich festgestellt, dass in den allermeisten Fällen das Ergebnis dieser Methode null war und ich nur in ganz seltenen Fällen meine gewünschte Entität zurückbekommen habe. Das hat, wie ich im Laufe der Zeit feststellte, die folgende Erklärung:
Die Methode getObject liefert genau dann ein Ergebnis ungleich null zurück, wenn die angeforderte Entität bereits im Frontend/Client/Browser vorhanden ist. Das ist genau dann der Fall, wenn diese über ein Binding oder ein vorheriges oModel.read() geladen wurde.
Ob das so ist lässt sich recht einfach überprüfen. Dazu kann man sich einfach das Model im Debugger des Browsers ausgeben lassen. Unter der Eigenschaft OData findet man dann alle bereits geladenen Entitäten.

> oModel.getObject("/DebitorSet('12345')")
< undefined

Wie hier zu sehen ist wurde meine gewünschte Entität noch nicht geladen, ergo, meine Methode liefert undefined zurück.
Eine weitere, recht einfache, Erklärung, dass noch nicht geladene Entitäten durch die Methode getObject nicht ausgegeben werden ist die Tatsache, dass getObject kein Call ans Backend ausführt. Dementsprechend kann die Methode auch nur Daten zurückgeben, die im Frontend schon vorhanden sind.

Hinweis:
In einem OData Model hat die Methode getObject die gleiche Funktionalität wie die Methode getData.

Wann benutze ich oModel.read(…)?

Die read Methode eines ODataModels ist laut API-Dokumentation eine andere Möglichkeit, Daten abzurufen. Der Unterschied zur Methode getObject ist, dass bei der read-Methode ein Call ans Backend stattfindet und die Daten von dort kommen. Im Frontend noch nicht vorhandene Daten können so zu jeder Zeit geladen bzw. nachgeladen werden.

 
oModel.read("/Debitor", {
  success: function(oData) {
    //Do something
  }
});

Zu berücksichtigen ist hier, dass ein read-Aufruf eine asynchrone Funktion ist. Das bedeutet, dass der Programmcode nicht wartet, bis die Funktion eine Antwort vom Backend erhält, sondern einfach weiter läuft. Sofern auf die Antwort gewartet werden soll muss der restliche Programmcode in einer Funktion aufgerufen werden, die über das success-Event der read-Funktion angesteuert wird. Dort sind die zurückgegebenen Daten über einen frei benennbaren Parameter abruf- und weiter verarbeitbar.

var fnCallback = function(oData) {
  if(oData.length > 0) {
    alert("Neuer Debitor " + oData[0] + " geladen!";
  }
};

oModel.read("/Debitor", {
  success: function(oData) {
    fnCallback(oData.results);
  }
});

Im Anschluss sind diese Daten dann auch mit der getObject-Methode direkt aus dem Model abrufbar.
Beim Aufruf der read-Methode können diverse Parameter angegeben werden. Klassisch sind erstmal zwei callback-Funktionen, eine für den erfolgreichen, eine für den nicht erfolgreichen Aufruf auf das Model.
Weitere Parameter können Filter sein, expand-Angaben, etc.
Hier ein Beispielaufruf der read-Methode:

oModel.read("/Debitor", {
  filters: [new sap.ui.model.Filter("name", "EQ", "Max Mustermann")],
  urlParameters: {
    "$expand": "ToAddress"
  },
  success: function(oData) {
    //TODO: Handling für fehlerhafte Calls
    fnCallback(oData.results);
  },
  error: function() {
    //Hinweis: Bei einem batch-Call werden auch fehlerhafte Aufrufe 
    //in die success-Function laufen, da der batch-Call an sich erfolgreich 
    //ist, selbst wenn ein einzelner Call fehlerhaft war. Dies gilt es zu 
    //berücksichtigen.
    MessageBox.show("Something went wrong!");
  }
});

Gut zu wissen: Bei einem Binding direkt im XML wird im Hintergrund immer die read-Methode aufgerufen!

Fazit

Meiner Erfahrung nach ist das Verständnis des Datenhandlings im OData Model einer der zentralen Aspekte in der Entwicklung von Anwendungen mit SAPUI5. Ich habe gemerkt, dass je besser das Verständnis auf meiner Seite als Entwickler war, desto qualitativ hochwertiger wurden meine Anwendungen.
Es lohnt sich, hier Zeit zu investieren um zu verstehen, wann/wie/welche Calls ans Backend stattfinden. Möglicherweise wird es überraschen, wie viele Calls die eigene Anwendung eigentlich absetzt. Vielleicht kann ja der ein oder andere eingespart werden, was die Performance auf jeden Fall positiv beeinflussen wird.

Hinweis:
Um die Backend-Calls zu überwachen einfach die Entwicklertools beim Start der Anwendung öffnen und auf die Netzwerkanalyse gehen.

Lies hier auch, wie man neue Daten mit Hilfe des OData Models erstellt

Bis dahin erstmal und: “Kiek mol wedder in!”

Du interessierst dich für Fiori und hast Lust coole Projekte mit uns zu machen? Wir suchen dich! Schau doch mal in unserer Stellenbeschreibung vorbei.

Über den Autor

Sebastian Kielhorn

Kommentar verfassen