dv-metadata について
dv-metadataは、Dataverseのメタデータを作成・変更するエージェントスキルです。テーブル、列、リレーション、フォーム、ビューを扱う開発者向けで、新規コンポーネントは環境で作成し、ソリューションとしてリポジトリへ出力する方法を重視します。
主な機能
- 01対象環境とソリューションの確認
- 02SDKによるテーブルと列の作成
- 03ルックアップフィールドとリレーションの作成
- 04フォームとビューの管理および公開
- 05環境の変更をソリューションへ出力
- 06列の命名規則と公開要件の適用
活用シーン
- 01Dataverseソリューションへのテーブルと列の追加
- 02ルックアップフィールドとテーブル間リレーションの作成
- 03フォームやビューを編集して変更を公開
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ dataverse-skills --skill dv-metadataインストール後、Claude Code で/dv-metadataを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/alternate-keys.mdforms-and-views.mdmetadata-propagation.md詳細
スキル:メタデータ — 変更の作成
セッションで最初のメタデータ変更を行う前に:
- 対象環境をユーザーに確認します — dv-overview のマルチ環境ルールを参照してください。
- ソリューションを確認します —「これらのコンポーネントをどのソリューションに追加しますか?」と尋ねます。
.envにSOLUTION_NAMEがある場合は、それでよいか確認します。まだソリューションが存在しない場合は、何かを作成する前に、必ずユーザーにソリューション名と発行元のプレフィックスを尋ねてください。発行元のプレフィックスは永続的で、これを使ってコンポーネントを作成した後は変更できません。
停止して、ユーザーに尋ねます:
「使用するソリューション名と発行元のプレフィックスを教えてください。プレフィックス(例:
contoso、lit、soc)は、すべてのテーブルと列で永続的に使用されます。」
次に、既存の発行元を照会して表示します。ユーザーが既存の発行元を再利用したい場合があります。
# Publisher discovery + solution creation — use SDK (never raw Web API).
# See dv-solution for the full publisher discovery flow.
publishers = client.records.list("publisher",
filter="customizationprefix ne 'none' and uniquename ne 'MicrosoftCorporation'",
select=["publisherid", "uniquename", "friendlyname", "customizationprefix"], top=10)
# MANDATORY: Show existing publishers to user and ask which to use or create new
ユーザーの確認後、SDK を使って作成します。
publisher_id = client.records.create("publisher", {
"uniquename": "<name>", "friendlyname": "<display>",
"customizationprefix": "<prefix>", # from user input, NOT hardcoded
"description": "<desc>",
})
solution_id = client.records.create("solution", {
"uniquename": "<SolutionName>", "friendlyname": "<Display Name>",
"version": "1.0.0.0",
"[email protected]": f"/publishers({publisher_id})",
})
ソリューション外でテーブルや列を作成しないでください。
- すべての SDK 呼び出しで
solution="<UniqueName>"を渡すか、すべての raw Web API 呼び出しに"MSCRM.SolutionName": "<UniqueName>"を含めます。
スキルの範囲
| 必要な操作 | 代わりに使用 |
|---|---|
| データレコードの作成、更新、削除 | dv-data |
| レコードの照会または読み取り | dv-query |
| ソリューションのエクスポートまたはデプロイ | dv-solution |
| ERP スキーマ | dv-query;ERP の振り分け |
変更の方法:環境優先
新しいテーブル、列、フォーム、ビューを作成するために、ソリューション XML を手作業で記述しないでください。
環境によるメタデータの検証は、エージェントが XML を編集するよりもはるかに信頼性が高いです。適切なワークフローは次のとおりです。
- 環境で変更を加えます。Dataverse MetadataService API(または利用可能な場合は
pacコマンド)を使います。 pac solution exportとpac solution unpackを使って、変更をリポジトリに取り込みます- 結果をコミットします
エクスポートされた XML は Dataverse 自体が生成するため、常に有効です。手書きの XML は壊れやすく、属性が 1 つ間違っていたり、要素が 1 つ欠けていたりするだけで、内容の分かりにくいエラーとともにインポートが失敗します。
ファイルを直接編集するのは、すでにリポジトリにあるものを変更する場合に限ります(例:既存のビューの列を調整する、またはすでに取り込んだフォームのレイアウトを変更する)。
CLI によるスキーマの確認: dataverse data describe --table account --include all --json(LogicalName を使用)。API の検出:dataverse api list --target dataverse。
テーブルの作成
データをインポートするために複数のテーブルを作成する場合は、このスキルの後半にある以下のセクションも参照してください。
- 冪等なテーブル作成 — 再実行可能なスクリプトのための、確認を先に行うパターン
- 代替キー — upsert に必要です。各テーブルの作成直後に作成してください
- メタデータの反映遅延とロック競合 — ロックエラーを避けるための段階的な作成
テーブルの作成には SDK を優先します。SDK では公開されていない OwnershipType、HasActivities、その他の高度なプロパティを完全に制御する必要がある場合に限り、raw Web API を使用します。
SDK の方法(デフォルトで使用):
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-metadata")
info = client.tables.create(
"new_ProjectBudget",
{"new_Amount": "decimal", "new_Description": "string"},
solution="MySolution",
primary_column="new_Name",
display_name="Project Budget", # human-readable name; plural auto-appends "s"
)
print(f"Created: {info['table_schema_name']}")
Web API による代替方法(OwnershipType、HasActivities、または SDK では公開されていないその他のプロパティが必要な場合に限る):
# Helper for Label boilerplate
def label(text):
return {"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [{"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": text, "LanguageCode": 1033}]}
entity = {
"@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
"SchemaName": "new_ProjectBudget",
"DisplayName": label("Project Budget"),
"DisplayCollectionName": label("Project Budgets"),
"Description": label(""),
"OwnershipType": "UserOwned",
"HasActivities": False, "HasNotes": False, "IsActivity": False,
"PrimaryNameAttribute": "new_name",
"Attributes": [{
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
"SchemaName": "new_name",
"DisplayName": label("Name"),
"RequiredLevel": {"Value": "ApplicationRequired"},
"MaxLength": 100, "IsPrimaryName": True,
}]
}
# POST to /api/data/v9.2/EntityDefinitions with MSCRM.SolutionUniqueName header
列の命名:*Id サフィックスとの衝突を避ける
通常の列名に Id サフィックスを付けないでください(例:prefix_CountryId)。Dataverse はルックアップを作成すると、Id サフィックスが付いたナビゲーションプロパティを自動生成します。同じ名前の通常の列が存在すると、スキーマ名の衝突によりルックアップの作成に失敗します。
- 誤り:
prefix_DepartmentId(int)— 自動生成されるルックアップと衝突します - 正しい:
prefix_SrcDepartmentIdまたはprefix_DepartmentSourceId
列の追加
SDK を使用する方法(推奨):
created = client.tables.add_columns(
"new_ProjectBudget",
{"new_Description": "string", "new_Amount": "decimal", "new_Active": "bool"},
)
print(created) # ['new_Description', 'new_Amount', 'new_Active']
サポートされている型文字列:"string"/"text"、"int"/"integer"、"decimal"/"money"、"float"/"double"、"datetime"/"date"、"bool"/"boolean"、"file"、および Enum サブクラス(ローカルの選択肢セット用)。
SDK を使用した選択肢(ピックリスト)列:
from enum import IntEnum
class BudgetStatus(IntEnum):
DRAFT = 100000000
APPROVED = 100000001
REJECTED = 100000002
created = client.tables.add_columns(
"new_ProjectBudget",
{"new_Status": BudgetStatus},
)
制約を設定する場合 — 型文字列だけでなく dict 形式の指定を渡します(キー:max_length、min_value、max_value、precision、format、required、display_name)。生の Web API を使わず、create と add_columns のどちらでも使用できます。
client.tables.add_columns("new_ProjectBudget", {
"new_Amount": {"type": "money", "min_value": 0, "max_value": 1000000000, "precision": 2},
"new_Comment": {"type": "memo", "max_length": 2000, "display_name": "Comment"},
})
既存の列は update_column(table, col, spec) または update_columns(table, {col: spec}) で直接更新できます。使用できるキーは同じです。書き込みの前に、すべての指定が検証されます。
ルックアップ列とリレーションシップ
SDK を使用する方法 — シンプルなルックアップ(推奨):
result = client.tables.create_lookup_field(
referencing_table="new_projectbudget",
lookup_field_name="new_AccountId",
referenced_table="account",
display_name="Account",
solution="MySolution",
)
print(f"Created lookup: {result.lookup_schema_name}")
SDK を使用する方法 — 1:N リレーションシップを詳細に制御:
from PowerPlatform.Dataverse.models.relationship import (
LookupAttributeMetadata,
OneToManyRelationshipMetadata,
CascadeConfiguration,
)
from PowerPlatform.Dataverse.models.labels import Label, LocalizedLabel
from PowerPlatform.Dataverse.common.constants import CASCADE_BEHAVIOR_REMOVE_LINK
lookup = LookupAttributeMetadata(
schema_name="new_AccountId",
display_name=Label(localized_labels=[LocalizedLabel(label="Account", language_code=1033)]),
)
relationship = OneToManyRelationshipMetadata(
schema_name="account_new_projectbudget",
referenced_entity="account",
referencing_entity="new_projectbudget",
referenced_attribute="accountid",
cascade_configuration=CascadeConfiguration(delete=CASCADE_BEHAVIOR_REMOVE_LINK),
)
result = client.tables.create_one_to_many_relationship(lookup, relationship, solution="MySolution")
print(f"Created: {result.relationship_schema_name}")
SDK を使用する方法 — 多対多リレーションシップ:
from PowerPlatform.Dataverse.models.relationship import ManyToManyRelationshipMetadata
relationship = ManyToManyRelationshipMetadata(
schema_name="new_ticket_knowledgebase",
entity1_logical_name="new_ticket",
entity2_logical_name="new_knowledgebase",
)
result = client.tables.create_many_to_many_relationship(relationship, solution="MySolution")
print(f"Created: {result.relationship_schema_name}")
Web API を使用する方法(SDK のパターンで不十分な場合の代替手段):
relationship = {
"@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
"SchemaName": "account_new_projectbudget",
"ReferencedEntity": "account",
"ReferencingEntity": "new_projectbudget",
"Lookup": {
"@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata",
"SchemaName": "new_AccountId",
"DisplayName": {"@odata.type": "Microsoft.Dynamics.CRM.Label",
"LocalizedLabels": [{"@odata.type": "Microsoft.Dynamics.CRM.LocalizedLabel",
"Label": "Account", "LanguageCode": 1033}]},
"RequiredLevel": {"Value": "None"}
}
}
# POST to /api/data/v9.2/RelationshipDefinitions
ルックアップの作成後 — @odata.bind のナビゲーションプロパティ:
このルックアップを設定するレコードを作成するには、@odata.bind に使用するナビゲーションプロパティ名が必要です。ナビゲーションプロパティ名は大文字と小文字を区別し、エンティティの $metadata と一致する必要があります(通常はルックアップフィールドの SchemaName です。例:new_AccountId)。
| ナビゲーションプロパティ名 | @odata.bind キー | エンティティセット |
|---|---|---|
new_AccountId | [email protected] | /accounts(<guid>) |
new_ParentTicketId | [email protected] | /new_tickets(<guid>) |
よくある間違い: [email protected] のように論理名(小文字)を使用すると、400 エラーが返されます。ナビゲーションプロパティ名は大文字と小文字を区別し、エンティティの $metadata と一致する必要があります。
ソリューションへのテーブルの追加
API でテーブルを作成した後、エクスポート時に取得されるよう、ソリューションに追加します。
pac solution add-solution-component \
--solutionUniqueName <SOLUTION_NAME> \
--component <SchemaName> \
--componentType 1 \
--environment <url>
コンポーネントの種類 1 は Entity(テーブル)です。種類コードの一覧については、dv-solution を参照してください。
または、Web API を使用します。
# POST to /api/data/v9.2/AddSolutionComponent
body = {
"ComponentId": "<entity-metadata-id>",
"ComponentType": 1, # 1 = Entity
"SolutionUniqueName": "<SOLUTION_NAME>",
"AddRequiredComponents": True
}
フォームとビュー
systemform(フォーム)と savedquery(ビュー)は通常のエンティティです。SDK のレコード CRUD を使って作成、読み取り、変更できます(urllib は不要です)。必要なのは、公開(バインドされていないアクション PublishXml)のみで、その際に dataverse api request を使用します。
クイックリファレンス:
- フォームの作成:
client.records.create("systemform", {...formxml..., "type": 2})(2=メイン、7=クイック作成、6=クイックビュー、11=カード)。 - フォームの変更:
records.list("systemform", filter=...)でテンプレートを取得 →formxmlを変更 →records.update("systemform", id, {...})→ 公開。 - 公開:
dataverse api requestでPublishXmlに POST します。変更を反映するには必須です。 - ビューの作成:
client.records.create("savedquery", {...fetchxml..., ...layoutxml...})(0=標準、1=高度な検索、2=関連、4=クイック検索)。
コード全文、テンプレートの手順、classid テーブル、公開方法については、references/forms-and-views.md を参照してください。
重要な不変条件:
- フォーム XML 内のすべての
id属性には、一意の GUID を指定する必要があります(str(uuid.uuid4()).upper())。 - Windows では GUID の生成に
python -cを使用しないでください。.pyファイルを作成してください。 - フォームは作成または変更のたびに公開する必要があります。公開しないと、変更がユーザーに表示されません。
ビジネスルール
Power Apps のメーカー ポータルでビジネスルールを作成してください。JSON/XAML で確実に記述するには複雑すぎます。作成後、ソリューションをエクスポートして展開し、結果をコミットしてください。
パブリッシャーのプレフィックス
すべてのカスタム スキーマ名には、ソリューションのパブリッシャー プレフィックス(例:new_、contoso_)を使用する必要があります。プレフィックスを確認するには:
pac solution list --environment <url>
または、最初の pull の後に solutions/<SOLUTION_NAME>/Other/Solution.xml を確認し、<CustomizationPrefix> を探してください。
FormXml の落とし穴
- すべての
id属性は、{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}形式の有効な GUID である必要があります。"general"のような文字列は使用しないでください。 labelidも GUID です。人が読める文字列ではありません。- サブグリッド コントロールには有効な
<ViewId>が必要です。既存の SavedQuery の GUID を指定してください。先にビューを作成してください。 - セル、セクション、タブ、コントロールの ID は、フォーム全体でそれぞれ一意である必要があります。
- コントロールの
classid値については、上記の classid テーブルを参照してください。
ヒント: Maker ポータルでフォームを作成し、pac solution export で取得してください。取得した XML をプログラムで作成する際のテンプレートとして使用します。
列の作成後:論理名を報告する
列を作成した後(Web API または MCP 経由)、実際の論理名を必ずユーザーに報告してください。列名は、ユーザーが想定していない形で正規化されたり、プレフィックスが付与されたりする場合があります。次のようなテーブルにまとめてください。
| 表示名 | 論理名 | 種類 |
|---|---|---|
| メール | cr9ac_email | String |
| ティア | cr9ac_tier | Picklist |
| 顧客 | cr9ac_customerid | Lookup |
これにより、ユーザーが誤った列名を使ってデータを挿入する際に発生する、後続処理の失敗を防げます。
よくある Web API エラーコード
| エラーコード | 意味 | 対処方法 |
|---|---|---|
0x80040216 | 一時的なメタデータ キャッシュ エラーです。列またはテーブルのメタデータがまだ反映されていません。 | 3~5 秒待ってから再試行してください。通常は 2 回目の試行で成功します。 |
0x80048d19 | ペイロード内のプロパティが無効です。フィールド名がテーブルのどの列とも一致していません。 | 論理列名を確認してください。EntityDefinitions(LogicalName='...')/Attributes を使用して検証します。 |
0x80040237 | スキーマ名がすでに存在します。 | 新しい列やテーブルを作成する前に、列やテーブルが存在するか確認してください。前回タイムアウトした呼び出しで作成されている可能性があります。 |
0x8004431a | 発行元プレフィックスが一致しません。 | すべてのスキーマ名で、ソリューションの発行元プレフィックスが使用されていることを確認してください。 |
0x80060891 | テーブル作成後、メタデータ キャッシュの準備ができていません。 | まず GET EntityDefinitions(LogicalName='...') を呼び出してキャッシュを更新し、その後再試行してください。 |
ユーザーに提示する前に、エラーコードは必ず平易な英語に置き換えてください。
メタデータの反映遅延とロック競合
テーブル、列、代替キーの作成後、Dataverse は内部メタデータ操作(インデックスの構築、キャッシュへの反映)を 3~30 秒間実行します。これらの処理中に別のメタデータ操作を送信すると、ロック競合エラーが発生します。
対策 — 操作を交互に行わず、段階的に作成します。 すべてのテーブルを作成 → 15~30 秒待機 → すべての代替キーを作成 → 15~30 秒待機 → すべてのルックアップを作成します。同じテーブルに対する操作を交互に行わないでください。
症状(次のいずれかが発生した場合、反映が完了していません):
- 選択肢列の作成が
0x80040216で失敗する - ルックアップの
@odata.bindが「Invalid property」で失敗する update_table(MCP)が「EntityId not found in MetadataCache」で失敗する- ルックアップまたは代替キーの作成が「another customization operation is running」で失敗する
一時的なロック エラーを処理する retry_metadata ヘルパーと、段階的な作成手順の全体については、references/metadata-propagation.md を参照してください。
既存のスキーマを確認する
モデルを変更する前に、既存の内容を確認してください。これらの読み取り専用呼び出しは、メタデータの辞書(PascalCase の名前)をそのまま返し、いつでも安全に実行できます。
このスキルで先に示した認証設定の
clientを前提とします(from auth import get_client)。
# typed=True returns type-specific fields (MaxLength, MinValue/MaxValue) in one request -- no @odata.type cast, no urllib
for col in client.tables.list_columns("account", typed=True):
print(col["LogicalName"], col.get("MaxLength"))
# get_column(table, col, typed=True) reads one column's constraints
# All relationships for one table (1:N, N:1, and N:N combined)
rels = client.tables.list_table_relationships("account")
for rel in rels:
print(f"{rel['SchemaName']} -> {rel.get('@odata.type')}")
# All relationships in the environment (optionally filtered)
all_rels = client.tables.list_relationships(select=["SchemaName", "ReferencedEntity", "ReferencingEntity"])
SQL クエリで使用できる列を調べる場合(仮想列/計算列は除外)、dv-query の client.query.sql_columns(table) も利用できます。
セッションの終了:リポジトリに取り込む
メタデータを扱うセッションの終了後は必ず、リポジトリに取り込む手順を実行してください。エクスポート/展開/コミットのコマンド全体については、dv-overview の「変更後の手順:リポジトリに取り込む」を参照してください。
作成時に MSCRM.SolutionName ヘッダーを使用した場合は、エクスポート前にコンポーネントが追加されていることを確認してください。
sol = client.records.list("solution",
filter="uniquename eq '<SOLUTION_NAME>'", select=["solutionid"], top=1).first()
if sol is not None:
components = client.records.list("solutioncomponent",
filter=f"_solutionid_value eq {sol['solutionid']}",
select=["componenttype", "objectid"])
print(f"{len(components)} components in the solution")
冪等性のあるテーブル作成
プログラムでテーブルを作成する場合(再実行される可能性のあるスキーマ設定スクリプトなど)は、まず確認するパターンを使用してください。作成前に client.tables.get() をクエリします。この方法は明示的で、無関係なエラーを隠さず、テーブルが作成されたか再利用されたかに応じてロジックを分岐できます。
def ensure_table(client, schema_name, columns, solution, primary_column="prefix_Name", display_name=None):
existing = client.tables.get(schema_name)
if existing:
print(f"Reusing: {schema_name}")
return existing
info = client.tables.create(schema_name, columns, solution=solution,
primary_column=primary_column, display_name=display_name)
print(f"Created: {info['table_schema_name']}")
return info
代替キー(Upsert に必須)
UpsertMultiple では、Dataverse が既存のレコードを識別するために使用する列に代替キーが必要です。すべてのインポートが冪等になるよう、スキーマ設定時にソースシステム ID の列(prefix_Src*Id)に必ず代替キーを作成してください。
クイックリファレンス:
- SDK 呼び出し:
client.tables.create_alternate_key(table, key_name, [columns], display_name=...)。複合キーの場合は、複数の列を渡します。 client.tables.get_alternate_keys(table)を使ってまず確認するパターンを採用し、すでに存在するキーはスキップしてください。ensure_alternate_keyヘルパーについてはreferences/alternate-keys.mdを参照してください。- インデックスの作成は非同期です。大規模なテーブルでは、使用前に
status == "Active"になるまでclient.tables.get_alternate_keys(table)をポーリングしてください。 - 制約:テーブルあたり最大 16 列/900 バイト/10 個のキー。有効な型は Integer/Decimal/String/DateTime/Lookup/OptionSet です。
SDK のコードサンプル(単一キー+複合キー+冪等性+ステータス確認)、エージェントが選択する列の判断ルール(DB ソースか Excel/CSV か)、失敗時の処理に関する注意事項については、references/alternate-keys.md を参照してください。
EntityDefinitions のフィルター制限
startswith() は EntityDefinitions のフィルターとしてサポートされていません。 このクエリは 400 エラーを返します。
GET /api/data/v9.2/EntityDefinitions?$filter=startswith(LogicalName,'new_') # BROKEN
複数のカスタムテーブルのメタデータを取得するには、テーブルごとにクエリしてください。
GET /api/data/v9.2/EntityDefinitions(LogicalName='new_projectbudget')?$select=LogicalName,EntitySetName
または、すべてをクエリしてからクライアント側でフィルターしてください。
GET /api/data/v9.2/EntityDefinitions?$select=LogicalName,EntitySetName
MCP によるテーブル作成時の注意事項
MCP の create_table または update_table を使用する場合:
- 列の型。 MCP はほとんどの型(テキスト、数値、ブール値、日時、選択肢/複数選択肢、ルックアップ/顧客、ファイル/画像)を処理できます。グローバル選択肢セット、N:N、代替キー、フォーム、ビューには SDK/Web API が必要です。
- タイムアウト/キャッシュの遅延。 作成時にタイムアウトや古いキャッシュが報告されることがあります。再試行または後続の
update_tableを行う前に、必ずdescribe('tables/{name}')を実行してください(テーブルが存在する場合は作成をスキップします)。 - 自己参照ルックアップ(親 → 同じテーブル)は、作成後に
update_tableで追加します。