SAPUI5 ODataV2-Model – Refresh Performance

Einleitung

Wer schon einmal eine Applikation in SAPUI5 geschrieben hat und dabei auf einen OData-Service zurückgegriffen hat, wird sicherlich schon einmal über folgendes Problem gestolpert sein:

Die Daten der aktuell angezeigten Entität oder des Entitätssets haben sich geändert. Soweit so gut. Nur die Anzeige verändert sich leider nicht.

Wenn die Daten sich hinter dem OData-Service verändert haben, müssen wir die Daten in jedem Fall manuell erneut Laden. Doch auch, wenn die Daten in derselben Applikation geändert werden, von der wir sie wieder abrufen wollen, kann es vorkommen, dass nicht alle Daten auch in der View erneuert werden. Das kann zum Bespiel vorkommen, wenn wir Daten verändern, die über Mapping-Tabellen an die aktuell an die View gebundene Entität verknüpft sind.

Für dieses Problem gibt es zum Glück eine einfache Lösung: refresh.

Mit dem Aufruf

 this.getView().getModel().refresh();

erneuert sich das ODataModel ganz von selbst und die gebundenen Views werden automatisch mit den neuen Daten versorgt.

Dabei ist allerdings Vorsicht geboten!

Die Performance des Refreshs ist ein nicht zu vernachlässigendes Kriterium. Ein kleines Beispiel folgt.

Beispiel

Dies ist eine Beispiel SAPUI5-Anwendung auf Basis des Northwind-ODataV2 Services. Sie besteht aus drei IconTabFiltern in einer IconTabBar.

Übersicht SAPUI5 Anwendung <em>NorthwindSampleApplication</em>
Übersicht SAPUI5 Anwendung NorthwindSampleApplication

In jedem der IconTabFilter befindet sich eine Tabelle. Die erste ist folgendermaßen an das Entitätsset Products gebunden:

<Table
items="{path:'/Products',parameters:{expand:'Category,Order_Details,Supplier',select:'ProductID,ProductName,UnitsInStock,UnitPrice,Category/CategoryName,Supplier/City'}}"
noDataText="Keine Daten" id="list0">
    <headerToolbar>
        <Toolbar>
            <Title text="Products" level="H2"/>
        </Toolbar>
    </headerToolbar>
    <columns>
        <Column>
            <Text text="Id"/>
        </Column>
        <Column>
            <Text text="Product Name"/>
        </Column>
        <Column>
            <Text text="Stock"/>
        </Column>
        <Column>
            <Text text="Unit Price"/>
        </Column>
        <Column>
            <Text text="Category"/>
        </Column>
        <Column>
            <Text text="Supplier Origin"/>
        </Column>
    </columns>
    <items>
        <ColumnListItem type="Active" press="onPress" id="item0">
            <cells>
                <Label text="{ProductID}"/>
                <Label text="{ProductName}"/>
                <Label text="{UnitsInStock}"/>
                <Label text="{UnitPrice}€"/>
                <Label text="{Category/CategoryName}"/>
                <Label text="{Supplier/City}"/>
            </cells>
        </ColumnListItem>
    </items>
</Table>

In gleicher Weise ist die Tabelle im mittleren IconTabFilter an das Entitätsset Orders und die Tabelle im rechten IconTabFilter an das Entitätsset Invoices gebunden.

Beispielhaft habe ich eine Objektansicht für die Produkte angelegt, die beim Klicken auf ein spezielles Produkt aus der Liste geöffnet wird:

<em>NorthwindSampleApplication</em> - Product Overview
NorthwindSampleApplication – Product Overview

Auf dieser Produktseite befinden sich zwei Buttons:

  1. Allgemeines Refresh
  2. Spezielles Refresh

Das Ziel eines „Refreshs“, also der Erneuerung der lokalen Daten im lokalen OData-Model, an dieser Stelle kann nur sein, alle Daten auf dieser aktuell angezeigten Seite zu erneuern.

In diesem Fall handelt es sich dabei um das Produkt mit der productID „1“. In dieser speziellen View werden keine Daten aus anderen Entitäten dargestellt, somit benötigen wir auch keinen $expand Ausdruck.

Alle weiteren Daten, die in anderen Views als der aktuellen liegen müssen beim Laden dieser View nicht aktualisiert werden. Um die Aktualität der Daten kümmern wir uns bei öffnen dieser Views.

Auch ist es nicht nötig das gesamte Entitätsset zu Laden, sei es auch mit $skip und $top, wir benötigen genau ein Produkt.

Soviel zu den Erwartung, schauen wir uns einmal die Effekte der beiden Buttons an.

Das allgemeine Refresh

Refresh des ODataV2-Models - <em>this.getView().getModel().refresh();</em>
Refresh des ODataV2-Models

Beim Drücken des ersten Knopfes wird das gesamte Model erneuert („Allgemeines Refresh“). Dazu nutzen wir folgenden Code:

Ein Einzeiler mit großen Folgen, denn schaut man sich die Calls an, die das ODataV2-Model im Hintergrund tätigt, staunt man nicht schlecht:

Refresh Model - Batch Calls
Refresh Model – Batch Calls

Im Hintergrund werden vier Batch-Calls verschickt, die Laufzeit liegt insgesamt bei circa 1,5 Sekunden. Schaut man sich den Inhalt der Batch-Calls an, erklärt sich diese Laufzeit:

Model Refresh - Laden des gesamten Product Entitysets
Model Refresh – Laden des gesamten Product Entitysets

Ganz am Anfang wird ein $count auf das Entitätsset Products aufgerufen, anschließend die ersten 100 Einträge aus diesem Entitätsset. Sicher nicht was geplant war, denn wir befinden uns auf der Übersichtsseite eines speziellen Produkts. Da werden nicht die ersten 100 Produkte benötigt, den $count brauchen wir hier auch nicht (Wie ihr unnötige $counts generell verhindern könnt, lest ihr hier).
Aber es wird noch schlimmer, scrollt man ein wenig runter:

Model Refresh - Laden des gesamten Order Entitätssets
Model Refresh – Laden des gesamten Order Entitätssets

Im selben Batch wird auch noch ein $count auf das Entitätsset Orders gemacht, auch dort laden wir noch einmal die ersten 100 Einträge.

Scrollen wir noch ein Stück, finden wir dasselbe dort auch noch einmal für das Entitätsset Invoice , also noch ein $count und noch einmal 100 Einträge.

Das ODataV2-Model scheint bei einem Refresh auf sich selbst also implizit ein Refresh auf alle vorhandenen Bindings durchzuführen – Egal aus welcher View.

Somit werden hier 3 AggregationBindings (aus den 3 Tabellen) und ein ElementBinding erneuert, gebraucht wird hier allerdings nur das letzte.

Das spezielle Refresh

Im Gegensatz zum ersten Knopf sieht die Logik hinter dem zweiten Knopf folgendermaßen aus:

Refresh des ODataV2-Models - <em>this.getView().getElementBinding().refresh();</em>
Spezielles Refresh

Wieder ein Einzeiler. Allerdings bedienen wir uns jetzt des Refreshs des speziellen Bindings. In diesem Fall also des ElementBindings der View, also des speziellen Produkts.

Ein Blick auf die Batch-Calls dieses Refreshs zeigt die Wirkung:

Spezielles Refresh - Batch Calls
Spezielles Refresh – Batch Calls

Im Hintergrund wird genau ein Batch verschickt. Laufzeit: 248ms.

Der Inhalt des Batches sieht wie folgt aus:

Spezielles Refresh - Batch Call Inhalt
Spezielles Refresh – Batch Call Inhalt

Der Batch-Call besteht aus genau einem GET auf das „Produkt 1“.

Analog zum ElementBinding klappt das spezielle Refresh übrigens auch mit Bindings auf Entitätssets, z.B. bei Listen oder Tabellen ;).

Dazu einfach:

Refresh des ODataV2-Models - <em>this.getView().byId("myTable").getBinding("items").refresh();</em>
Spezielles Refresh für AggregationBindings

Fazit

Wer in seiner SAPUI5-Anwendung einmal auf das Problem trifft, sein ODataV2-Model erneuern zu müssen, sollte darauf achten, welche Daten tatsächlich erneuert werden müssen. Das allgemeine Refresh ist vermutlich in wenigen der Fälle die Antwort.

Schon in diesem kleinen Beispiel ist der Zeitvorteil messbar, ohne dass die Menge der Datensätze sich besonders an der Laufzeit der Batch-Calls bemerkbar macht. Denn die Datensätze des Northwind OData-Services sind vergleichsweise klein:

  • /Products/$count:  77
  • /Orders/$count: 830
  • /Invoices/$count: 2155

Vor allem durch den sehr geringen Aufwand beim Programmieren lohnt es sich kurz darüber nachzudenken, ob das allgemeine Refresh an dieser Stelle jetzt wirklich nötig ist.

Über den Autor

Torben Rogge

Ein Kommentar zu “SAPUI5 ODataV2-Model – Refresh Performance”

Kommentar verfassen