dv-data について
dv-data は、Dataverse のレコード作成、更新、削除、インポートを扱うスキルです。操作量や要件に応じて、MCP、Dataverse CLI、公式 Python SDK の選択を案内します。
主な機能
- 01レコードの作成・更新・削除
- 02代替キーを使った Upsert
- 03レコードの一括操作
- 04CSV インポートと参照解決
- 05ファイル列へのアップロード
活用シーン
- 01少数レコードの対話的な書き込み
- 02関連レコードを参照する CSV のインポート
- 03再実行可能な一括 Upsert
- 04Dataverse のファイル列へのアップロード
MCP 対応
少量の対話的な書き込みには、create_record、update_record、delete_record などの Dataverse MCP ツールを推奨します。1 回の呼び出しで扱えるのは最大 25 レコードです。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ dataverse-skills --skill dv-dataインストール後、Claude Code で/dv-dataを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/erp-writes.mdmulti-table-fk-import.mdsample-data-generation.md詳細
スキル:データ — 作成、更新、削除、一括インポート
このスキルでは Python と Dataverse CLI を使用します。 Dataverse スクリプトには Node.js、JavaScript、その他の言語を使用しないでください。
npm installを実行しようとしている、または.jsファイルを作成しようとしている場合は、そこで STOP してください。ルールから外れています。概要スキルの厳格なルールを参照してください。
ERP の例外(計画のみのリクエストを含む): dv-overview を読み込んでから、references/erp-writes.md を読んでください。ERP への書き込みと DMF インポートには、以下の Dataverse SDK のガイダンスは適用されません。操作を実行しないよう求められていても、スキルの読み込みとローカルのリファレンスの参照は可能です。
すべてのデータ書き込み操作には、Microsoft Power Platform Dataverse の公式 Python クライアント SDK を使用してください。
公式 SDK: https://github.com/microsoft/PowerPlatform-DataverseClient-Python
PyPI パッケージ: PowerPlatform-Dataverse-Client(公式パッケージはこれだけです。dataverse-api やその他の非公式パッケージは使用しないでください)
ステータス: GA(1.1.0、Production/Stable)
スキルの範囲
| 必要な作業 | 代わりに使用 |
|---|---|
| レコードのクエリまたは読み取り | dv-query |
| テーブル、列、リレーションシップ、フォーム、ビューの作成 | dv-metadata |
| ソリューションのエクスポートまたはデプロイ | dv-solution |
| ERP への書き込み | references/erp-writes.md を参照 |
書き込みに MCP、CLI、SDK のどれを選ぶか
CLI の最短手順: dataverse auth who でアクティブなプロファイルが表示されれば、CLI コマンド(data create/update/delete/upsert/associate/upload)をすぐに使用できます。.env、auth.py、pip は不要です。SDK と一括操作には、引き続きワークスペースのセットアップが必要です。
MCP ツール(create_record、update_record、delete_record)が利用できる場合、 少量かつ対話的な書き込みには、スクリプトが不要な最速の方法です。1 回の呼び出しにつき最大 25 件のレコードを処理できます。Dataverse CLI(dataverse data create/update/upsert/delete)は、単一レコードの書き込み、関連付け/関連付け解除、ファイルのアップロードを、ヘッドレスのワンライナーで処理します。SDK は、25 件を超える一括書き込み、データ変換、再試行ロジック、CSV インポート、SDK でしか実行できない操作(代替キーを使った upsert — MCP には upsert ツールがありません)の標準的な選択肢です。作業の量と内容に合った方法を選んでください。
書き込みをスクリプト化する場合は、手書きの HTTP ではなく SDK を使用する
MCP/CLI/SDK の選択は、上記の機能に基づいて行います(概要の ツールの機能/厳格なルール 2 も参照してください)。このセクションの範囲はより限定的です。スクリプトで書き込むと決めたら、 SDK の「対応」リストにある操作には、手書きの urllib/requests ではなく SDK を使用してください。SDK が認証、ページング、再試行を処理するため、これらを再実装する必要がありません。SDK で対応していないまれな操作には、手書きの urllib ではなく dataverse api のエスケープハッチを使用してください。
正しいインポート(完全なスクリプトでは、必ず先に sys.path.insert を実行します。セットアップを参照):
from auth import get_client
SDK が対応する操作に対して誤った方法:
from auth import get_token, load_env # WRONG for SDK-supported ops
import requests # WRONG for SDK-supported ops
get_token() と requests は、管理された手段がない実際の機能不足(グローバル オプション セット、バインドされていないアクションなど)に限って使用します。それでも、管理された dataverse api のエスケープハッチを優先してください。フォーム/ビュー、集計、N:N の読み取りにはすべて SDK が対応しています。dv-query と dv-metadata を参照してください。
この SDK が対応する内容(データ操作)
- レコードの書き込み:作成、更新、削除
- 書き込みワークフロー内でのレコード読み取り(例:ルックアップの解決)。単独のクエリについては dv-query を参照
- Upsert(代替キーに対応)
- 一括操作:
CreateMultiple、UpdateMultiple、UpsertMultiple、一括削除(records.delete(table, [ids])はサーバー側の BulkDelete アクションを実行します)、複数操作のバッチ処理(client.batch) - ファイル列へのアップロード(128MB を超えるファイルはチャンク分割)
- HTTP 接続プーリングに対応したコンテキストマネージャー
この SDK がサポートしていないこと
フォーム/ビュー(systemform/savedquery)は通常のレコードです。client.records.* で作成/変更し(dv-metadata を参照)、records.list(expand=...) で N:N を読み取ります。以下の実際の未対応機能には、生の urllib よりも、管理された dataverse api の回避策を優先してください。
- グローバルオプションセット — dv-metadata を参照
- N:N レコードの関連付け — CLI の
dataverse data associate、またはPOST /api/data/v9.2/<entity>(<id>)/<nav-property>/$ref $applyによる集計 —client.query.fetchxml()を使用してください。dv-query を参照- バインドされていないアクション(例:
PublishXml、InstallSampleData) —dataverse api request/invoke
Dataverse CLI のデータ例(コピーしてすぐに使用可能)
すべての dataverse コマンドは、スキルの帰属情報に --context(グローバルフラグ)を指定します。
# Create a record (--table is the EntitySet name)
dataverse data create --table accounts --data '{"name":"Contoso"}' --return --json --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Update by GUID
dataverse data update --table accounts --id <guid> --data '{"name":"Contoso (updated)"}' --json --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Upsert by alternate key (idempotent — safe to re-run)
dataverse data upsert --table accounts --key "accountnumber='ACC-001'" --data '{"name":"Contoso Ltd"}' --json --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Delete (--no-confirm skips the prompt)
dataverse data delete --table accounts --id <guid> --no-confirm --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Associate two records (N:N or lookup)
dataverse data associate --table accounts --id <guid> --relationship contact_customer_accounts --related contacts --related-id <contact-guid> --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Disassociate (N:N — pass --related-id; clear a lookup — omit --related-id)
dataverse data disassociate --table accounts --id <guid> --relationship contact_customer_accounts --related-id <contact-guid> --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Upload a file to a file column (--table takes LogicalName, not EntitySet)
dataverse data upload --table account --id <guid> --column new_document --file report.pdf --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Describe entity schema (attributes, relationships, actions)
dataverse data describe --table account --include all --json --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Invoke a discovered custom API by name (use 'api list' to find names)
dataverse api invoke <CustomApiName> --target dataverse --param Input=value --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
# Raw API escape hatch for built-in actions (--target is required)
dataverse api request --target dataverse --path "/api/data/v9.2/WhoAmI" --context "app=dataverse-skills/<ver>;skill=dv-data;agent=<agent>"
セットアップ
import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client
# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-data")
get_client(skill) は、認証、環境 URL、プラグインの帰属情報(User-Agent のタグ付け)を処理します。scripts/auth.py を参照してください。
最後まで実行するスクリプトでは、自動的に接続をクリーンアップするため、with DataverseClient(...) as client: で囲んでください(推奨)。ノートブックや対話型セッションでは、上記のようにクライアントを明示するほうが簡単です。
フィールド名の大文字・小文字の規則
この規則を誤ると、400 エラーが発生します。
| プロパティの種類 | 規則 | 例 | 使用箇所 |
|---|---|---|---|
| 構造(列) | LogicalName — 常に小文字 | new_name、new_priority | レコードのペイロードキー |
| ナビゲーション(ルックアップ) | Navigation Property Name — 大文字・小文字を区別し、$metadata と一致 | new_AccountId | @odata.bind キー |
SDK は構造キーを自動的に小文字に変換しますが、@odata.bind キーの大文字・小文字は保持します。
レコードを作成する
guid = client.records.create("new_ticket", {
"new_name": "Ticket 001",
"new_priority": 100000002, # choice column — integer value, not string
"[email protected]": "/accounts(<account-guid>)",
})
print(f"Created: {guid}")
@odata.bind に関する注意事項:
- キーには Navigation Property Name を使用します:
[email protected](SDK は大文字・小文字を自動的に保持しますが、スキーマ名と一致させるのが正しい形式です) - 値は
"/<EntitySetName>(<guid>)"です。例:"/accounts(<guid>)" - ルックアップ列を作成した直後は、挿入する前に 5~10 秒待ってください。メタデータの反映に時間がかかると、「Invalid property」エラーが発生します。
- 選択肢列には文字列ではなく整数値を使用します:
"new_priority": 100000002("High"ではありません)
よく使う @odata.bind のパターン
| ルックアップ | 正しいキー | 誤ったキー |
|---|---|---|
カスタム:new_AccountId | [email protected] | [email protected] |
システムのポリモーフィック:customerid | [email protected] | [email protected] |
システム:parentcustomerid | [email protected] | [email protected] |
Navigation Property Name を確認する
SDK 経由でルックアップを作成した後は、result.lookup_schema_name がナビゲーションプロパティ名です。
既存のシステムテーブルについては、次のようにクエリします。
GET /api/data/v9.2/EntityDefinitions(LogicalName='<entity>')/ManyToOneRelationships
?$select=ReferencingEntityNavigationPropertyName,ReferencedEntity
レコードを更新する
client.records.update("new_ticket", "<record-guid>",
{"new_status": 100000001})
レコードを削除する
# Single record
client.records.delete("new_ticket", "<record-guid>")
# Bulk delete — pass a list of ids; runs the server-side BulkDelete action (one async job, not N calls)
job_id = client.records.delete("new_ticket", [id1, id2, id3])
# use_bulk_delete=False deletes each id sequentially instead
一括作成(SDK は内部で CreateMultiple を使用)
records = [{"new_name": f"Ticket {i}", "new_priority": 100000000} for i in range(500)]
guids = client.records.create("new_ticket", records)
print(f"Created {len(guids)} records")
ボリュームの目安:単発のレコードには CLI の dataverse data create を使用します。MCP の create_record は 1 回の呼び出しにつき最大 25 件をまとめて処理します。より大規模な一括処理には SDK の CreateMultiple を使用してください。
重要: SDK はすべてのレコードを単一の POST で CreateMultiple に送信します。自動的なチャンク分割は行いません。Dataverse に固定のレコード件数上限はありません。制約となるのはペイロードサイズとリクエストのタイムアウト(SDK の POST のデフォルト:120 秒)です。より大きなデータセットでは、スクリプト内で必ずチャンク分割してください。以下の bulk_upsert および bulk_create ヘルパーは適応型のチャンク分割を使用します。1,000 件から開始し、成功したら倍増(最大 4,000 件)、ペイロード/タイムアウトの失敗時は半減し、最後に成功したサイズを上限とします。列数の少ないテーブルは、列数の多いテーブルより大きなチャンクを処理できます。
一括更新
# Broadcast same change to multiple records
client.records.update("new_ticket",
[id1, id2, id3],
{"new_status": 100000001})
DataFrame からの書き戻し
pandas DataFrame からレコードを作成または更新するには、client.dataframe 名前空間の create/update を使用します。dv-query に記載されていますが、書き込み操作のため、データ書き込みワークフローに含めてください。
# Update records — DataFrame must include the primary key column
client.dataframe.update("opportunity", df_updates, id_column="opportunityid")
# Create records — returns a Series of new GUIDs
guids = client.dataframe.create("opportunity", df_new_records)
client.dataframe による書き込みの詳細は、dv-query を参照してください。読み取りには client.query.builder(...).execute().to_dataframe() を使用します。
Upsert(代替キー)
冪等性があります。同じインポートを再実行しても重複は作成されません。代替キーは、事前にテーブルで定義しておく必要があります。詳しくは dv-metadata を参照してください。
レコード本体に代替キーの列を含めないでください。 代替キーはレコードを特定するもので、レコード本体には設定するデータを含めます。同じ列が両方に含まれていると、UpsertMultiple は「予期しないエラーが発生しました」というエラーで失敗します(単一の upsert では許容されますが、一括処理では許容されません)。
from PowerPlatform.Dataverse.models.upsert import UpsertItem
client.records.upsert("account", [
UpsertItem(
alternate_key={"accountnumber": "ACC-001"},
record={"name": "Contoso Ltd", "description": "Primary account"},
),
UpsertItem(
alternate_key={"accountnumber": "ACC-002"},
record={"name": "Fabrikam Inc"},
),
])
CSV からの一括インポート
再実行する可能性があるインポート(実際のほとんどのケース)には、
create()ではなく、代替キーを使うUpsertItemを使用してください。詳しくはreferences/multi-table-fk-import.mdを参照してください。ここで示すcreate()のパターンは、一度限りの読み込み専用です。
| 件数 | ツール | 理由 |
|---|---|---|
| 1 件 | CLI dataverse data create または MCP create_record | スクリプトは不要です |
| 2~25 件 | MCP create_record | 1 回の呼び出しで最大 25 件をまとめて処理できます |
| 25 件以上 | SDK client.records.create(table, list) | CreateMultiple を使用します。大規模なデータセットは分割してください(まず 1K 件から始めて調整します) |
import csv, os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client
# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-data")
with open("data/customers.csv", newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
records = [{"new_name": row["name"], "new_email": row["email"]} for row in rows]
# SDK sends all in one POST — chunk to avoid payload/timeout limits
# Start at 1000; for narrow tables (few columns) you can go higher
chunk_size = 1000
for i in range(0, len(records), chunk_size):
guids = client.records.create("new_customer", records[i:i + chunk_size])
print(f"Imported {i + len(guids)}/{len(records)} customers", flush=True)
インポート時のルックアップ解決
CSV に人間が読みやすいキー(例:customer_email)がある一方で、Dataverse には GUID が必要な場合は、ルックアップ辞書を使って事前に解決してください。
# Build email -> GUID map first
email_to_guid = {}
for r in client.records.list("new_customer", select=["new_customerid", "new_email"]):
email_to_guid[r["new_email"]] = r["new_customerid"]
# Use it during import
records = []
for row in rows:
customer_guid = email_to_guid.get(row["customer_email"])
if not customer_guid:
print(f"Skipping row — unknown email: {row['customer_email']}")
continue
records.append({
"new_channel": row["channel"],
"[email protected]": f"/new_customers({customer_guid})", # verify entity set name via EntityDefinitions
})
guids = client.records.create("new_interaction", records)
システムテーブルの必須フィールドの確認
システムテーブル(account、contact、opportunity)に一括作成する前に、次の手順を実行してください。
- 使用予定の最小限のペイロードでテストレコードを 1 件作成します。
HttpError400 が発生した場合、エラーメッセージに不足している必須フィールドが示されます。- 必須フィールドの中にはプラグインによって強制され、
describeには表示されないものがあります。 - テストレコードを削除してから、一括作成に進みます。
外部キー依存関係がある複数テーブルのインポート
外部キー関係のある複数のテーブルにデータをインポートする場合、依存関係の順序に従って、UpsertItem と代替キーを使ってインポートする必要があります(冪等性があり、再実行しても安全です)。
クイックリファレンス:
- ソース ID 列、代替キー、ルックアップリレーションシップを含むテーブルを作成します(dv-metadata を参照)。
ThreadPoolExecutorを使って、レベル 0(外部キー依存関係なし)のテーブルを並列にインポートします。各テーブル内のチャンクは順次処理します(書き込みを並行するとデッドロックします)。- インポート結果をクエリして、ソース ID から GUID へのマップを作成します(upsert は GUID を返しません)。
- 依存関係レベルごとに繰り返します。レベル 1 では、
@odata.bindにレベル 0 のマップが必要です。
アダプティブな bulk_upsert ヘルパー、複合キーの処理、インポート後の検証、初回用の bulk_create バリエーションを含む全体のパターンについては、references/multi-table-fk-import.md を参照してください。
重要な不変条件(リファレンスを読まない場合も適用されます):
- 同じレベルのテーブル間では並列化し、レベル間およびテーブル内のチャンクは順次処理します。
- 代替キーの列をレコード本体にも含めないでください。
UpsertMultipleが失敗します。 - エグゼキューター内でテーブルごとに失敗を捕捉してください。1 つのテーブルの失敗で他のテーブルまで停止してはいけません。
chunk_size=1000から始め、ヘルパーが適応的に増やします。
エラー処理
from PowerPlatform.Dataverse.core.errors import HttpError
try:
guid = client.records.create("new_ticket", {"new_name": "Test"})
except HttpError as e:
print(f"Status {e.status_code}: {e.message}")
if e.details:
print(f"Details: {e.details}")
# 400 — bad field name, @odata.bind format, or missing required field
# 403 — check security roles
# 404 — table or record not found
# 429 — rate limited; SDK retries automatically, reduce batch size if persistent
ERP データの書き込み
ERP にリンクされた環境では、ERP エンティティへの書き込みに Python SDK は使用しません。詳しくは references/erp-writes.md を参照してください。
Windows スクリプトの注意事項
.pyファイルでは ASCII 文字のみを使用してください。曲線引用符やエムダッシュを使うと、Windows でSyntaxErrorが発生します。- 複数行のコードに
python -cを使用しないでください。 代わりに.pyファイルを作成してください。 - スクリプト内で GUID を生成します。シェルのバッククォートによる置換ではなく、
str(uuid.uuid4())を使用してください。
サンプルデータの生成
現実的なサンプルレコードをインラインで生成します。スキーマ駆動でテーブルに依存せず、PIIを安全に扱えるデフォルト値を使用します(メールアドレスは@example.com、電話番号は555-01xx)。
クイックリファレンス: 環境、件数、テーブルを確認 → 必須列を取得するためにEntityDefinitions(LogicalName='<table>')/Attributes?$filter=AttributeOf eq nullをクエリ → AttributeType(String / Memo / Integer / DateTime / Picklist / など)に応じて振り分け → client.records.create()を実行(件数が10以上の場合はCreateMultipleを使用)。
スキーマ駆動のfake()テンプレート、EntityDefinitionsクエリ、安全性ルールについては、references/sample-data-generation.mdを参照してください。
主な不変条件:
- ユーザーが値を明示的に指定しない限り、Lookup、Uniqueidentifier、State、Status、Owner、Customerフィールドはスキップしてください。
UserLocalizedLabelはnullの場合があるため、安全に参照してください。
確認フローの例
N件のサンプルレコードを生成する(破壊的操作 — スニペットをプレビューし、環境を確認する):
- ❌ 「どの環境を対象にしますか?DataverseのURLを指定してください。」
- ✅ 「
TABLE=\"contact\"、COUNT=20でサンプルデータ生成スニペットを実行します。アクティブなpac auth list環境に対してCreateMultipleを使用し、メールアドレスには.example.com、電話番号には555-01xxを使用します。続行するには確認してください。別の環境を指定することもできます。」
カスタムエンティティのサンプルデータ(スキーマが不明 — 説明文だけで十分):
- ❌ 「エンティティに関する情報がもっと必要です。必須フィールドは何ですか?」
- ✅ 「カスタムエンティティですね。必須列を確認するために
cr123_projectのEntityDefinitionsをクエリし、各列をAttributeTypeに応じたジェネレーターに割り当てて5件のレコードをインラインで生成し、client.records.create(\"cr123_project\", records)を呼び出します。続行するには確認してください。別の件数を指定することもできます。」