Creare item SharePoint in Batch con Power Automate
Solitamente quando si devono creare una serie di item su SharePoint Online in Power Automate l'approccio classico è creare un ciclo con l'azione Apply to each e all'interno del ciclo invocare, uno alla volta, l'azione Create item per SharePoint Online.
Ovviamente l'approccio funziona bene, ma presenta dei limiti quando si vogliono inserire molti item, già con 20 o più si nota un incremento notevole dei tempi di esecuzione dell'ordine di parecchi minuti.
Si tratta di chiamare in POST la url
e passare nel body una stringa con l'elenco di tutte le url che puntano alla lista con i relativi dati da aggiornare, tipo questa
In questo caso tutte create item:
Ogni changeset inizia con la stringa separatore dei changeset --changeset_<guid 2>, seguita dalle righe che rappresentano gli headers:
Venendo al corpo della singola richiesta/changeset:
La prima riga è composta da:
Anche in questo caso subito dopo abbiamo gli headers, in questo caso uno solo (Content-Type), terminati sempre da una linea vuota.
Il body in questo caso è l'oggetto JSON con i dati da usare per creare il nuovo item.
Alla fine di tutti i changeset troviamo la stringa di chiusura --changeset_<guid 2>-- (doppi meno alla fine).
Step 2: generiamo la stringa con tutti i changeset tramite un Apply to each (vedi dopo come evitare Apply to each) andando in append sulla variabile
Step 3: Eseguiamo la richiesta HTTP
che viene eseguito in pochi secondi anche con 50 items o più
L'azione ritornerà sempre success, anche se alcuni degli item non sono stati inseriti correttamente.
Per capire quali azioni sono andate in errore o meno, va controllata la risposata JSON
Qui troviamo il dettaglio delle azioni con lo stato di esecuzione.
Ovvero statusCode=201 (Created) in caso di successo nella creazione dell'item, oppure 400 (Bad Request) in caso di errore:
Un metodo veloce per capire se tutto è andato a buon fine è quello di fare uno split della stringa di risposta e contare le righe e togliere 1
se coincide con il numero di item che dovevano essere inseriti, tutto è andato a buon fine
Ad esempio un ciclo su 50 elementi impiega circa 21 secondi
Nel caso di insert, dove le url che identificano la risorsa sono tutte uguali, si può fare un ottimizzazione.
Anziché usare Apply to each, si possono usare una serie di azioni Data Operation e la funzione split per portare i tempi a 0 secondi (sempre su 50 items).
Step 2: Join per creare una stringa di tutti gli items separati da un separatore (in questo caso @@@###!!!@@@ che rappresenta una stringa che difficilmente comparirà nel templare o nei dati)
Step 3: Replace del separatore con il template e l'aggiunta di un template all'inizio.
La formula è questa:
Questa è giusto un introduzione, per maggiori dettagli vedi Make batch requests with the REST APIs.
Ovviamente l'approccio funziona bene, ma presenta dei limiti quando si vogliono inserire molti item, già con 20 o più si nota un incremento notevole dei tempi di esecuzione dell'ordine di parecchi minuti.
HTTP Request Batch
Un possibile approccio per ridurre i tempi è quello di eseguire la creazione di tutti gli items in Batch con un unica azione Send an HTTP request to SharePoint.Si tratta di chiamare in POST la url
URL
https://<tenantName>.sharepoint.com/_api/$batch
Text: Corpo/Body della richiesta
--batch_c02f1b8d-deb1-486b-87db-d8ff6c7190cb
Content-Type: multipart/mixed;boundary=changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Lenght: 840
Content-Transfer-Encoding: binary
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Type: application/http
Content-Transfer-Encoding: binary
POST https://<tenantName.sharepoint.com/_api/web/lists/getbytitle('<listName>')/items HTTP/1.1
Content-Type: application/json;odata=nometadata
{"Title":"Batch 1","Date":"2022-10-06T19:30:42.7847708Z","Note":"aaa"}
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Type: application/http
Content-Transfer-Encoding: binary
POST https://<tenantName.sharepoint.com/_api/web/lists/getbytitle('<listName>')/items HTTP/1.1
Content-Type: application/json;odata=nometadata
{"Title":"Batch 2","Date":"2022-10-06T19:31:43.0191527Z","Note":"bbb"}
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad--
--batch_c02f1b8d-deb1-486b-87db-d8ff6c7190cb--
Batch
In pratica va creata una richiesta batch delimitata dall'identificatore (boundary string) iniziale --batch_<guid> e finale --batch_<guid>-- (doppi meno alla fine)Text: Struttura del Batch
--batch_c02f1b8d-deb1-486b-87db-d8ff6c7190cb
Content-Type: multipart/mixed;boundary=changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Lenght: <lunghezza del change set senza la riga di chiusura>
Content-Transfer-Encoding: binary
... contenuto dei changeset
--batch_c02f1b8d-deb1-486b-87db-d8ff6c7190cb--
Teoricamente non è necessario usare un guid, ma il suo uso è più sicuro perché garantisce che non ci sia un testo simile nel corpo della richiesta.
Il valore di Content-Lenght è la lunghezza della stringa che rappresenta tutti i changeset passati, senza contare la riga di chiusura dei changeset.
Il valore di Content-Lenght è la lunghezza della stringa che rappresenta tutti i changeset passati, senza contare la riga di chiusura dei changeset.
Dopo Content-Transfer-Encoding va lasciata una riga vuota senza spazi.
Changeset
I changeset saranno all'interno del batch, tanti quante sono le azioni da compiere.In questo caso tutte create item:
Text: Struttura dei Changeset con 2 insert
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Type: application/http
Content-Transfer-Encoding: binary
POST https://<tenantName.sharepoint.com/_api/web/lists/getbytitle('<listName>')/items HTTP/1.1
Content-Type: application/json;odata=nometadata
{"Title":"Batch 1","Date":"2022-10-06T19:30:42.7847708Z","Note":"aaa"}
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad
Content-Type: application/http
Content-Transfer-Encoding: binary
POST https://<tenantName.sharepoint.com/_api/web/lists/getbytitle('<listName>')/items HTTP/1.1
Content-Type: application/json;odata=nometadata
{"Title":"Batch 2","Date":"2022-10-06T16:31:43.0191527Z","Note":"BatchIdc02f1b8d-deb1-486b-87db-d8ff6c7190cb, ChangesetId 6d3d8d17-488d-418e-a3a5-a8e4510d41ad"}
--changeset_6d3d8d17-488d-418e-a3a5-a8e4510d41ad--
- Content-Type: application/http
- Content-Transfer-Encoding: binary
Venendo al corpo della singola richiesta/changeset:
Text: Singola richiesta
POST https://<tenantName.sharepoint.com/_api/web/lists/getbytitle('<listName>')/items HTTP/1.1
Content-Type: application/json;odata=nometadata
{"Title":"Batch 2","Date":"2022-10-06T16:31:43.0191527Z","Note":"BatchIdc02f1b8d-deb1-486b-87db-d8ff6c7190cb, ChangesetId 6d3d8d17-488d-418e-a3a5-a8e4510d41ad"}
- metodo HTTP da usare (POST)
- uno spazio
- la url che rappresenta la risorsa
- un altro spazio
- la stringa fissa HTTP/1.1
Anche in questo caso subito dopo abbiamo gli headers, in questo caso uno solo (Content-Type), terminati sempre da una linea vuota.
Il body in questo caso è l'oggetto JSON con i dati da usare per creare il nuovo item.
Alla fine di tutti i changeset troviamo la stringa di chiusura --changeset_<guid 2>-- (doppi meno alla fine).
Realizzazione in Power Automate
Step 1: Creiamo 2 variabili con i Guid da usare come delimitatori dei batch e dei changeset e una altra variabile per contenere la stringa con tutti i changesetStep 2: generiamo la stringa con tutti i changeset tramite un Apply to each (vedi dopo come evitare Apply to each) andando in append sulla variabile
Power Automate: Value
--changeset_@{variables('MultipartChangesetId')}
Content-Type: application/http
Content-Transfer-Encoding: binary
POST @{variables('SiteUrl')}_api/web/lists/getbytitle('@{variables('ListTitle')}')/items HTTP/1.1
Content-Type: application/json;odata=verbose
{
__metadata: {"type": "SP.Data.TestBatchInsertListItem" },
Title: "@{items('Apply_to_each:_InputData')?['Title']}",
Date: "@{items('Apply_to_each:_InputData')?['Date']}",
Note: "@{items('Apply_to_each:_InputData')?['Note']}"
}
In questo caso la mia sorgente dati era un array di nome ArrInputData.
Step 3: Eseguiamo la richiesta HTTP
Power Automate: Headers
{
"Content-Type": "multipart/mixed;boundary=batch_@{variables('MultipartBatchId')}"
}
Power Automate: Body
--batch_@{variables('MultipartBatchId')}
Content-Type: multipart/mixed;boundary=changeset_@{variables('MultipartChangesetId')}
Content-Lenght: @{length(variables('StringBatchItemsToSave'))}
Content-Transfer-Encoding: binary
@{variables('StringBatchItemsToSave')}
--changeset_@{variables('MultipartChangesetId')}--
--batch_@{variables('MultipartBatchId')}--
Anche se dura pochi secondi, la richiesta ha portato a termine tutti gli inserimenti, infatti nella risposta c'è il risultato di ogni singolo inserimento (vedi dopo).
Errori
Una nota particolare va fatta riguardo agli errori.L'azione ritornerà sempre success, anche se alcuni degli item non sono stati inseriti correttamente.
Per capire quali azioni sono andate in errore o meno, va controllata la risposata JSON
Qui troviamo il dettaglio delle azioni con lo stato di esecuzione.
Ovvero statusCode=201 (Created) in caso di successo nella creazione dell'item, oppure 400 (Bad Request) in caso di errore:
Controllando le proprietà LOCATION è possibile ricavare gli Id degli item inseriti.
Un metodo veloce per capire se tutto è andato a buon fine è quello di fare uno split della stringa di risposta e contare le righe e togliere 1
URL
sub(length(split(string(outputs('Send_an_HTTP:_Batch_create')?['body/$multipart']),'"statusCode":201')),1)
Ottimizzazione Apply to each
Guardando l'esecuzione di può facilmente vedere come la maggior parte del tempo è spesa nell'Apply to each per costruire la stringa con i changeset, mentre l'esecuzione dell'azione Send HTTP è quasi immediata (pochi secondi).Ad esempio un ciclo su 50 elementi impiega circa 21 secondi
Suppongo che i tempi elevati siano legati al fatto che il ciclo Apply to each, ogni tanto, venga sospeso dalla piattaforma per distribuire le risorse nell'abiente condiviso, quindi i tempi di esecuzione sono difficilmente prevedibili.
Nel caso di insert, dove le url che identificano la risorsa sono tutte uguali, si può fare un ottimizzazione.
Anziché usare Apply to each, si possono usare una serie di azioni Data Operation e la funzione split per portare i tempi a 0 secondi (sempre su 50 items).
Implementazione
L'implementazione prevede 3 stepStep 1: Creazione di una variabile con il template del changeset (nell'esempio un compose ma può essere anche una variabile)Step 2: Join per creare una stringa di tutti gli items separati da un separatore (in questo caso @@@###!!!@@@ che rappresenta una stringa che difficilmente comparirà nel templare o nei dati)
Step 3: Replace del separatore con il template e l'aggiunta di un template all'inizio.
La formula è questa:
Power Automate: Concat and Replace
concat(string(outputs('Compose:_BatchChangesetTemplate')), replace(body('Join'), '@@@###!!!@@@', string(outputs('Compose:_BatchChangesetTemplate'))))
Conclusioni
Gli update in batch, ma anche le letture di più items, possono ridurre notevolmente i tempi di esecuzione..Questa è giusto un introduzione, per maggiori dettagli vedi Make batch requests with the REST APIs.