コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

Power AutomateでSharePoint Listに対してBulk Insertを行う


スポンサードリンク

はじめに

この記事はPower Automateで色々なデータソースに対してBulk Insertを行うSharePoint List編です。

登録を行う元データは上記記事の「Bulk Insert」セクションを参照してください。

Power Automateではデータソースに対してレコードを作成するアクションを標準で搭載してはいますが、どれも1件ずつの登録しかできません。

これでは例えば500件のデータを登録したい場合など、ループアクションが必要になり、APIコール数もこれだけで500回も消費してしまいます。

このような事態を避けるためにも1回のアクションで複数件のデータを登録する方法をこの記事ではまとめています。

あくまでもここで紹介しているのは一例に過ぎないのでご注意ください。

SharePoint Listに対してBulk Insertを行う

登録を行うSharePoint Listには以下のようなテーブルが存在しています。

列名 種類 説明 元データとのマッピング
Title 標準列(1行テキスト) GUIDが含まれています GUID
Number 数値 数値が含まれています Number
Text 1行テキスト 文字列が含まれています Text
Date 日付と時刻 日付が含まれています Date

SharePoint Listにデータ登録を行うにはMicrosoft GraphもしくはSharePoint REST APIを活用します。

learn.microsoft.com

どちらも$batchセグメントによる複数要求の単一HTTP 呼び出しを利用しています。

Graph API

learn.microsoft.com

SharePoint REST

learn.microsoft.com

Graph APIを利用する

このやり方では、一度に最大でも20件の要求しか行えないので500件のデータを登録したい場合は25回のアクション実行が必要になってきます。

また、このやり方ちょっと厄介でして以下の制限事項を私は確認して嵌りました。  

  • Office 365 GropsコネクターHTTP 要求を送信するではbatch要求を行えない

    "クエリ操作で無効な HTTP メソッド 'POST' が検出されました。クエリ操作では、HTTP メソッドとして 'GET' のみがサポートされます。"

  • Microsoft Graph REST API を介した SharePoint REST 操作(_api/v2.1/$batch)ではdependsOnオプションが効いていない?(これは要検証。現象としては→)最後の要求の"POST"しか結果として反映されていない。

という問題があります。

ということで、Graph APIはHTTPアクションを用いて実行することにしました。

なおこのやり方を利用する場合は、認証方法としてAADにアプリを登録する必要がありますので以下を参考にアプリを登録しておいてください。

Microsoft Graph アプリケーションを登録する - Azure AD B2C | Microsoft Learn

気を付ける点とすればAPIのアクセス許可ですかね。
委任にするのかアプリケーションにするのかなど。

SiteIdの取得

まずは"SiteId"の取得を行います。

以下アクションで取得しています。

URI

_api/v2.1/sites/[domain].sharepoint.com

domainは各自の環境に直してください。

* 「Office 365 Groups」コネクタの「HTTP要求を送信します」アクションでGraph APIを実行するのでもOK

リクエストの作成

実行するリクエスト(登録するデータ)を作成していきます。
なおここでは、先ほど記載したbatch処理の実行上限(20件)は考慮しません。(細かくいうと、'dependsOn'の箇所は考慮していますが)
それは後のアクションで行います。

リクエストは以下アクションで作成します。

開始(from)

range(0, length(outputs('Origin_Data')))

マップ(select)

{
  "id": "@{add(item(), 1)}",
  "@{ if(
    And(not(equals(item(), 0)), greater(mod(add(item(), 1), 20), 0)),
    'dependsOn',
    ''
)}": ["@{ if(
    And(not(equals(item(), 0)), greater(mod(add(item(), 1), 20), 0)),
    item(),
    ''
)}"],
  "method": "POST",
  "url": "/sites/@{body('SharePoint_に_HTTP_要求を送信します(siteId)')?['Id']}/lists/mySampleLists/items",
  "body": {
    "fields": {
      "Title": "@{outputs('Origin_Data')?[item()]?['GUID']}",
      "Number": @{outputs('Origin_Data')?[item()]?['Number']},
      "Text": "@{outputs('Origin_Data')?[item()]?['Text']}",
      "Date": "@{outputs('Origin_Data')?[item()]?['Date']}"
    }
  },
  "headers": {
    "Content-Type": "application/json"
  }
}

'id'及び'dependsOn'で連番の数値を設定したいので、「開始」にはデータではなく0~データの長さまでの連番が設定された配列を設定しています。

'dependsOn'プロパティの箇所の記載がめんどくさいですね。
もっといいかき方ないかなー。。。と思ってますがこういう風にかかないと、JSON形式が正しくないぞ!って怒られちゃうんですよね。

この'dependsOn'では20区切りにしたときに、最初に登録されるアイテムはプロパティが設定されないようにしています。(そうしないと、実行できないからね!)

"url": "/sites/@{body('SharePoint_に_HTTP_要求を送信します(siteId)')?['Id']}/lists/mySampleLists/items",

ここの"mySampleLists"は自身のリスト名に置き換えてください。

  "body": {
    "fields": {
      "Title": "@{outputs('Origin_Data')?[item()]?['GUID']}",
      "Number": @{outputs('Origin_Data')?[item()]?['Number']},
      "Text": "@{outputs('Origin_Data')?[item()]?['Text']}",
      "Date": "@{outputs('Origin_Data')?[item()]?['Date']}"
    }
  }

ここは自身の登録したいデータとなりますね。

20件ずつbatch処理を実行する

batch処理の上限(20件)を考慮して、先ほど作成したリクエストを分割実行していきます。

こんな感じ。

ループ回数分だけ連番の配列を作成しています。

注意する点としては、例えば50件登録するときは3回ループが必要になるので単純に20で割るだけじゃダメってことですかね。

range(
    0, 
    add(
        div(
            length(outputs('Origin_Data')), 
            20
        ), 
        if(
            equals(
                mod(length(outputs('Origin_Data')), 20), 
                0
            ), 
            0, 
            1
        )
    )
)

20件に分割するのは以下アクションで行っています。

from

body('選択(SPOLists)')

where

@and(
   greaterOrEquals(
       int(item()?['id']), 
       mul(
           items('Apply_to_each'), 
           20
       )
   ), 
   less(
       int(item()?['id']), 
       mul(
           add(
               items('Apply_to_each'), 
               1
           ), 
       20
       )
   )
)

1回目のループでは'id'が1~19を、2回目のループでは'id'が20~39を・・・となるようにフィルターして取得しています。

ここでフィルターした結果をもとにBodyを作成します。

{
  "requests": @{body('アレイのフィルター処理')}
}

ここは説明不要ですかね。

これらの情報を元にHTTPアクションでGraph APIを実行しています。

URI

https://graph.microsoft.com/v1.0/$batch

ヘッダー

{
    "Accept": "application/json",
    "Content-Type": "application/json"
}

本文

outputs('作成(SPOLists)')

ちなみに認証はこんな感じ。

AADのアプリケーション登録にて設定した値をここに設定します。

「対象ユーザー」は https://graph.microsoft.com でOK。

これでGraph APIを利用してSPO ListにBulk Insertができました。

これはHTTPアクションが必要なのでプレミアムコネクタの利用が必要ですね。

SharePoint REST APIを利用する

この方法を利用する場合の同時実行の制限値ですが公式ドキュメントに記載ないんですよねー。。。
知っている方いれば教えてください!

SharePoint REST APIのbatch実行だと、Bodyに設定する値はJSON値ではなくなります。

以下のような形式が必要です。

--batch_[batch GUID]
Content-Type: multipart/mixed; boundary="changeset_[changeset GUID]"
Host: https://[domain].sharepoint.com
Content-Transfer-Encoding: binary

--changeset_[changeset GUID]
Content-Type: application/http
Content-Transfer-Encoding:binary

POST https://[domain].sharepoint.com/sites/[siteName]/_api/lists/getbytitle('[listName]')/items HTTP/1.1
Content-Type: application/json;type=entry

[Registered Data in JSON]

--changeset_[changeset GUID]
Content-Type: application/http
Content-Transfer-Encoding:binary

POST https://[domain].sharepoint.com/sites/[siteName]/_api/lists/getbytitle('[listName]')/items HTTP/1.1
Content-Type: application/json;type=entry

[Registered Data in JSON]

--changeset_[changeset GUID]
Content-Type: application/http
Content-Transfer-Encoding:binary

POST https://[domain].sharepoint.com/sites/[siteName]/_api/lists/getbytitle('[listName]')/items HTTP/1.1
Content-Type: application/json;type=entry

[Registered Data in JSON]

--changeset_[changeset GUID]
Content-Type: application/http
Content-Transfer-Encoding:binary

POST https://[domain].sharepoint.com/sites/[siteName]/_api/lists/getbytitle('[listName]')/items HTTP/1.1
Content-Type: application/json;type=entry

[Registered Data in JSON]

--changeset_[changeset GUID]--
--batch_[batch GUID]--

こんな形式になるようにデータを加工します。

"batch GUID"と"changeset GUID"は同じ値を使うので以下のように作成しておいてください。

guid()

GUIDにしていますが、「create」みたいな固定の名称でもいいですよ。
対象がわかればいいだけなので。

また、Hostも複数回利用することになるので

こんな感じで作成しています。
自身のサイトに置き換えておいてくださいー

登録データの作成

最初のここと、

--batch_[batch GUID]
Content-Type: multipart/mixed; boundary="changeset_[changeset GUID]"
Host: https://[domain].sharepoint.com
Content-Transfer-Encoding: binary

--changeset_[changeset GUID]
Content-Type: application/http
Content-Transfer-Encoding:binary

最後のここは、

--changeset_[changeset GUID]--
--batch_[batch GUID]--

固定で、この中身が繰り返し処理になるところなのでこの中身を「選択」アクションで作成してあげます。

開始(from)

outputs('Origin_Data')

マップ(select)

concat(
    '--changeset_', outputs('Changeset_ID'), uriComponentToString('%0A'),
    'Content-Type: application/http', uriComponentToString('%0A'),
    'Content-Transfer-Encoding:binary', uriComponentToString('%0A'),
    uriComponentToString('%0A'),
    'POST ', outputs('Host'), '/sites/development/_api/lists/getbytitle(''mySampleLists'')/items HTTP/1.1', uriComponentToString('%0A'),
    'Content-Type: application/json;type=entry', uriComponentToString('%0A'),
    uriComponentToString('%0A'),
    '{', uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Title": "', item()?['GUID'], '",' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Number": ', item()?['Number'], ',' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Text": "', item()?['Text'], '",' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Date": "', item()?['Date'], '"' , uriComponentToString('%0A'),
    '}'
)

実行結果でみたときにインデント揃えたかったのでuriComponentToString 関数で成形しています。

改行部分以外はなくていけるのかな?試してないからわからないです。

'/sites/development/_api/lists/getbytitle(''mySampleLists'')/items HTTP/1.1'

ここの"develop"は自身のサイト名に置き換えてください。
"mySampleLists"は自身のリスト名に置き換えてください。
(こいつらも外だしした方がよかったかも)

    '{', uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Title": "', item()?['GUID'], '",' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Number": ', item()?['Number'], ',' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Text": "', item()?['Text'], '",' , uriComponentToString('%0A'),
        uriComponentToString('%09'), '"Date": "', item()?['Date'], '"' , uriComponentToString('%0A'),
    '}'

ここは自身の登録したいデータに置き換わりますね。

お気づきの方もいるかもしれませんが、batchで登録する際は"__metadata"が不要になります。(あるとエラーになります。)

単体登録だとこんな感じのBodyが必要でしたね。

learn.microsoft.com

{
    "__metadata": {
        "type": "SP.Data.[ListItemEntityTypeFullName]ListItem"
    },
    "Title": "title"
}

Bodyを作成する

先ほど作成した登録データをもとにBodyを作成します。

先ほど固定といった箇所と、先ほど作成した結果をつなげるだけですね。

--batch_@{outputs('Batch_ID')}
Content-Type: multipart/mixed; boundary="changeset_@{outputs('Changeset_ID')}"
Host: @{outputs('Host')}
Content-Transfer-Encoding: binary

@{join(body('選択(SPO_REST)'), concat(uriComponentToString('%0A'), uriComponentToString('%0A')))}

--changeset_@{outputs('Changeset_ID')}--
--batch_@{outputs('Batch_ID')}--

REST APIの実行

これまでの情報をもとにREST APIを実行します。

URI

_api/$batch

ヘッダー

{
    "Content-Type": "multipart/mixed; boundary=batch_@{outputs('Batch_ID')}",
    "Expect": "100-continue"
}

ボディ

outputs('作成(SPO_REST)')

気を付けるべき点は'batch'と'changeset'の値ですかね。
ヘッダー、およびボディで異なるもの呼び出そうとしていると動かないです。

REST APIだと、標準コネクタで実施できますね。
ただ、Bodyの組み立てが面倒

おわりに

同時実行の件や、有償/無償で考えてもREST APIで実行したほうがよさそうですね。

問題は若干こっちの方が難易度高いかな?どうかな?といった点ですかね。


スポンサードリンク