Power Automate mit SharePoint Search REST API beschleunigen
Einführung
In diesem "Quick Tipp" stelle ich eine pragmatische Lösung vor, wie man Power Automate Flows in Verbindung mit der SharePoint Search REST API beschleunigen kann.
Überblick der Problematik bei der Verwendung der SharePoint Search REST API
Aus meiner Sicht hat die SharePoint Search REST API [1] einige Fehler oder Nachteile, die das Arbeiten mit der Schnittstelle erschweren. Dies trifft besonders auf die Verarbeitung von JSON-Daten in Power Automate zu, wo die Möglichkeiten der Programmierung durch die Umgebung relativ eng begrenzt sind. Meiner Meinung nach sind die drei größten Schwachpunkte der API:
- die Daten, die die Schnittstelle liefert, sind mehrfach ineinander verschachtelt. Die eigentlichen Ergebnisse findet man in Zellen, die sich in Zeilen befinden, die zu einer Tabelle gehören.
- das Tabellen-Format bedingt auch, dass die eigentlichen Ergebnisse nicht in Form von Schlüssel-Wert-Paaren zurück geliefert werden. Das macht den schnellen Zugriff auf bestimmte Werte extrem kompliziert und sorgt in Power Automate für sehr lange Laufzeiten.
- die Anzahl der zurückgegebenen Schlüssel-Wert-Paare läßt sich nicht genau beeinflussen. Mittels des Parameters SelectProperties lässt sich zwar bestimmen, welche Eigenschaften man auf jeden Fall im Ergebnis erhalten möchte. Aber der Rückgabewert wird nicht auf die in diesem Parameter definierten Eigenschaften beschränkt.
JSON Schema der SharePoint Search REST API
{
"type": "object",
"properties": {
"d": {
"type": "object",
"properties": {
"query": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"ElapsedTime": {
"type": "integer"
},
"PrimaryQueryResult": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"CustomResults": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"results": {
"type": "array"
}
}
},
"QueryId": {
"type": "string"
},
"QueryRuleId": {
"type": "string"
},
"RefinementResults": {},
"RelevantResults": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"GroupTemplateId": {},
"ItemTemplateId": {},
"Properties": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"Key": {
"type": "string"
},
"Value": {
"type": "string"
},
"ValueType": {
"type": "string"
}
},
"required": [
"Key",
"Value",
"ValueType"
]
}
}
}
},
"ResultTitle": {},
"ResultTitleUrl": {},
"RowCount": {
"type": "integer"
},
"Table": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"Rows": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"Cells": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"Key": {
"type": "string"
},
"Value": {
"type": "string"
},
"ValueType": {
"type": "string"
}
},
"required": [
"__metadata",
"Key",
"Value",
"ValueType"
]
}
}
}
}
},
"required": [
"__metadata",
"Cells"
]
}
}
}
}
}
},
"TotalRows": {
"type": "integer"
},
"TotalRowsIncludingDuplicates": {
"type": "integer"
}
}
},
"SpecialTermResults": {}
}
},
"Properties": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"Key": {
"type": "string"
},
"Value": {
"type": "string"
},
"ValueType": {
"type": "string"
}
},
"required": [
"Key",
"Value",
"ValueType"
]
}
}
}
},
"SecondaryQueryResults": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"results": {
"type": "array"
}
}
},
"SpellingSuggestion": {},
"TriggeredRules": {
"type": "object",
"properties": {
"__metadata": {
"type": "object",
"properties": {
"type": {
"type": "string"
}
}
},
"results": {
"type": "array"
}
}
}
}
}
}
}
}
}
Das oben gezeigte JSON Schema wird so von Power Automate mit dem Schritt JSON analysieren vorgeschlagen, wenn man die ursprünglichen JSON-Daten einer Abfrage an die SharePoint Search REST API verwendet. Daran sieht man schon, dass die Verarbeitung solcher JSON-Daten nicht trivial ist. Zudem ist das von Power Automate vorgeschlagene JSON Schema nicht verwendbar, weil manche Eigentschaften fest dem Typ String zugewiesen werden, die aber in der Realität teilweise auch Null-Werte enthalten. Es ist also angeraten, die Type-Definitionen wie folgt zu ändern, falls man mit diesem Schema und auf die herkömliche Weise JSON-Daten in Power Automate verarbeiten möchte.
Angepaßte Typ-Definition in JSON
"type": {
"type": ["string", "null"]
}
Ein Result wird von der SharePoint Search REST API folgendermaßen zurückgegeben, wobei ich hier die Anzahl der Schlüssel-Wert-Paare bereits gekürzt habe.
JSON Objekt (gekürzt)
{
"Cells": {
"results": [{
"__metadata": {
"type": "SP.KeyValue"
},
"Key": "IsDocument",
"Value": "false",
"ValueType": "Edm.Boolean"
},
{
"__metadata": {
"type": "SP.KeyValue"
},
"Key": "UniqueId",
"Value": "{E5FD972D-DCE1-4F04-B5A6-94FBE20C1E40}",
"ValueType": "Edm.String"
},
{
"__metadata": {
"type": "SP.KeyValue"
},
"Key": "Path",
"Value": "https://xxx",
"ValueType": "Edm.String"
}
]
}
}
Problemstellung beim Verarbeiten von Daten der SharePoint Search REST API
Möchte man auf eine bestimmte Eigenschaft eines Ergebnisses der SharePoint Search REST API zugreifen, so muss man im Rückgabewert der Schnittstelle nach dieser Eigenschaft suchen. Als Beispiel möchte ich eine Liste aller UniqueID erhalten, die bei der Suche nach dem Begriff "Test" von der SharePoint-Suche als Ergebnis zurückgegeben werden. Meine Such-Abfrage lautet:
GET-Request an SharePoint Search REST API
/_api/search/query?querytext='test'&selectproperties='UniqueId'&rowlimit=10
Entsprechend dem oben dargestellten JSON-Schema enthält der Rückgabewert der Schnittstelle 10 Zellen-Ergebnisse (Cells->results). Jedes dieser Ergebnisse enthält wiederum 18 Schlüssel-Wert-Paare - und trotz der Angabe des Parameters "selectproperties='UniqueId'". Um an die gewünschen Daten zu gelangen, muss man also den vollständigen Rückgabewert in mehreren Schleifen komplett durchlaufen. Zuerst muss man alle Cells durchlaufen. In jeder Zelle müssen alle Keys durchlaufen werden. Jede Schlüssel muss dann mit dem gesuchten Schlüssel UniqueId verglichen werden. Liegt eine Übereinstimmung vor, hat man also das gesuchte Schlüssel-Wert-Paar gefunden, dann muss der aktuelle Wert notiert werden (an ein Array angefügt werden). Die nachfolgenden Bilder vermitteln den Aufbau des Flows entsprechend.
Darstellung der zweifachen Schleife in Power Automate
Das Durchlaufen der zweichfachen Schleife dauert in Power Automate bei 10 Datensätzen ca. 25,7 Sekunden - viel zu lange!
Generell sind Schleifen in Power Automate langsam. Im vorliegenden Fall wird die innere Schleife 10-mal gestartet, wodurch der ganze Flow sehr lange läuft.
Lösungsansatz: JSON-Daten vor der Verarbeitung konvertieren
Die Idee ist, die innere Schleife zu vermeiden. Um das zu erreichen, müssten die JSON-Daten vor der Verarbeitung in ein Schema überführt werden, welches für die Verarbeitung besser geeignet ist. Ich stelle mir ein Schema mit einem richtigen Schlüssel-Wert-Paar vor, beispielsweise so:
Konvertiertes JSON Objekt (gekürzt)
[
{
"IsDocument": "false",
"UniqueId": "{E5FD972D-DCE1-4F04-B5A6-94FBE20C1E40}",
"Path": "https://xxx"
}
]
Das konvertierte JSON Objekt macht einen direkten Zugriff auf den Wert eines Schlüssels möglich. Dadurch entfällt die innere Schleife, in der nach dem gewünschten Schlüssel gesucht wird.
Aufgrund der Limitierungen im Standardfunktionsumfang von Power Automate habe ich mich hauptsächlich auf die Funktion replace konzentriert. Der nachfolgende Ausdruck in einem Verfassen-Schritt sorgt für die notwendige Konvertierung der JSON-Daten.
Der unten genannte Konvertierungsschritt funktioniert nur, wenn zuvor beim HTTP Request folgende HTTP Header verwendet wurden:
Name | Wert |
accept |
application/json;odata=verbose |
content-type |
application/json;odata=verbose |
JSON-Daten konvertieren
@{json(replace(replace(replace(replace(replace(replace(replace(replace(replace(string(outputs('HTTP-Anforderung_an_SharePoint_senden')?['body']['d']['query']['PrimaryQueryResult']['RelevantResults']['Table']['Rows']['results']),'"Key":',''),',"Value"',''),'{"__metadata":{"type":"SP.SimpleDataRow"},"Cells":{"results":',''),']}}',']'),'"__metadata":{"type":"SP.KeyValue"},',''),'{"','"'),'"}','"'),'["','{"'),'"]','"}'))}
Dabei werden die JSON-Daten aus dem Rückgabewert der REST API zuerst in eine Zeichenkette verwandelt. Danach werden nacheinander bestimmte Zeichenketten gelöscht bzw. ersetzt. Am Ende wird aus der restlichen Zeichenkette wieder ein JSON Objekt gemacht.
Mit den konvertieren JSON-Daten ist in Power Automate nur noch eine Schleife notwendig. In dieser Schleife kann mit einem Schlüssel direkt auf dessen Wert zugegriffen werden, also:
@{items('Auf_alle_anwenden')?['UniqueId']}
Durch die vorherige Konvertierung der JSON-Daten verringert sich die Dauer des Flows auf 2,26 Sekunden.
Fazit zu vorgeschlagenen Beschelunigung in Verbindung mit der SharePoint Search REST API
Die vorgeschlagene Lösung ist sehr pragmatisch. Replace-Aktionen an einem JSON-String sind immer kritisch zu sehen. Andererseits ist der Zeitgewinn durch das verbesserte JSON-Schema nach der Konvertierung enorm.