mcpField
D
dv-data

dv-data

github.com/microsoftデータ・データベース248212 回インストール今日更新
リポジトリ

dv-data について

dv-data は、Dataverse のレコード作成、更新、削除、インポートを扱うスキルです。操作量や要件に応じて、MCP、Dataverse CLI、公式 Python SDK の選択を案内します。

主な機能

  1. 01レコードの作成・更新・削除
  2. 02代替キーを使った Upsert
  3. 03レコードの一括操作
  4. 04CSV インポートと参照解決
  5. 05ファイル列へのアップロード

活用シーン

  1. 01少数レコードの対話的な書き込み
  2. 02関連レコードを参照する CSV のインポート
  3. 03再実行可能な一括 Upsert
  4. 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.md
multi-table-fk-import.md
sample-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_record1 回の呼び出しで最大 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. 使用予定の最小限のペイロードでテストレコードを 1 件作成します。
  2. HttpError 400 が発生した場合、エラーメッセージに不足している必須フィールドが示されます。
  3. 必須フィールドの中にはプラグインによって強制され、describe には表示されないものがあります。
  4. テストレコードを削除してから、一括作成に進みます。

外部キー依存関係がある複数テーブルのインポート

外部キー関係のある複数のテーブルにデータをインポートする場合、依存関係の順序に従って、UpsertItem と代替キーを使ってインポートする必要があります(冪等性があり、再実行しても安全です)。

クイックリファレンス:

  1. ソース ID 列、代替キー、ルックアップリレーションシップを含むテーブルを作成します(dv-metadata を参照)。
  2. ThreadPoolExecutor を使って、レベル 0(外部キー依存関係なし)のテーブルを並列にインポートします。各テーブル内のチャンクは順次処理します(書き込みを並行するとデッドロックします)。
  3. インポート結果をクエリして、ソース ID から GUID へのマップを作成します(upsert は GUID を返しません)。
  4. 依存関係レベルごとに繰り返します。レベル 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)を呼び出します。続行するには確認してください。別の件数を指定することもできます。」

コメント