mcpField
D
dv-metadata

dv-metadata

github.com/microsoft数据与数据库248226 次安装今天更新
代码仓库

关于 dv-metadata

dv-metadata 是指导 Agent 在 Dataverse 中创建和修改元数据的技能,面向构建表、列、关系、表单及视图的开发者。它强调先在环境中通过 SDK 或 API 创建,再导出解决方案到代码库,而非手写新组件 XML。

主要功能

  1. 01先确认目标环境与解决方案
  2. 02通过 SDK 创建表和列
  3. 03创建查找字段及表关系
  4. 04管理表单与视图并发布
  5. 05将环境变更导出到解决方案
  6. 06遵循列命名与发布要求

适用场景

  1. 01为 Dataverse 解决方案添加表与列
  2. 02建立查找字段及表间关系
  3. 03修改表单和视图并发布更改

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/dataverse-skills --skill dv-metadata

安装后在 Claude Code 中输入/dv-metadata确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
alternate-keys.md
forms-and-views.md
metadata-propagation.md

详细介绍

技能:元数据 — 进行更改

在会话中首次更改元数据之前:

  1. 向用户确认目标环境 — 请参阅 dv-overview 中的多环境规则。
  2. 确认解决方案 — 询问“这些组件应归入哪个解决方案?”如果 .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})",
})

切勿在解决方案之外创建表或列。

  1. 在每次 SDK 调用中传入 solution="<UniqueName>",或者在每次原始 Web API 调用中包含 "MSCRM.SolutionName": "<UniqueName>"。

技能范围

需求改用
创建、更新或删除数据记录dv-data
查询或读取记录dv-query
导出或部署解决方案dv-solution
ERP 架构dv-query;ERP 路由

如何进行更改:环境优先

不要手动编写解决方案 XML 来创建新表、列、窗体或视图。

环境验证元数据的可靠性远高于由 Agent 编辑 XML。正确的工作流如下:

  1. 在环境中进行更改,使用 Dataverse MetadataService API(或适用时使用 pac 命令)
  2. 通过 pac solution export + pac solution unpack 将更改拉取到代码库
  3. 提交结果

导出的 XML 由 Dataverse 自行生成,始终有效。手写 XML 很脆弱 — 即使只有一个属性不正确或缺少一个元素,也会导致导入失败,并且错误信息晦涩难懂。

只有在编辑代码库中已有文件时才直接编写文件(例如,调整现有视图的列,或修改已拉取到代码库的窗体布局)。

CLI 架构检查: dataverse data describe --table account --include all --json(使用 LogicalName)。API 发现:dataverse api list --target dataverse。


创建表

如果要为数据导入创建多个表,还请参阅本技能后续的以下部分:

  • 幂等表创建 — 可重复运行脚本的先检查模式
  • 备用键 — upsert 所必需;每创建一个表后立即创建
  • 元数据传播延迟和锁争用 — 分阶段创建以避免锁错误

创建表时优先使用 SDK — 只有在需要 SDK 未公开的 OwnershipType、HasActivities 或其他高级属性时,才使用原始 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 备用方式(仅当需要 SDK 未公开的 OwnershipType、HasActivities 或其他属性时使用):

# 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);适用于 create 和 add_columns,无需使用原始 Web API:

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 POST PublishXml——要使更改生效,必须执行此操作。
  • 创建视图: client.records.create("savedquery", {...fetchxml..., ...layoutxml...})(0=标准,1=高级查找,2=关联,4=快速查找)。

完整代码、模板编写方法、classid 表格和发布说明请参见:references/forms-and-views.md。

关键规则:

  • 窗体 XML 中的所有 id 属性都必须是唯一的 GUID(str(uuid.uuid4()).upper())。
  • 在 Windows 上不要使用 python -c 生成 GUID——请编写 .py 文件。
  • 每次创建或修改窗体后都必须发布,否则用户看不到更改。

业务规则

请在 Power Apps maker portal 中创建业务规则。使用 JSON/XAML 编写业务规则过于复杂,难以确保可靠。创建后,导出并解包解决方案,然后提交结果。


发布者前缀

所有自定义架构名称都必须使用解决方案的发布者前缀(例如 new_、contoso_)。查找前缀:

pac solution list --environment <url>

或者在首次拉取后查看 solutions/<SOLUTION_NAME>/Other/Solution.xml——查找 <CustomizationPrefix>。


FormXml 注意事项

  • 所有 id 属性都必须是有效的 GUID,格式为 {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}。不要使用 "general" 这样的字符串。
  • labelid 也是 GUID,不能使用便于阅读的字符串。
  • 子网格控件需要有效的 <ViewId>,且必须是现有 SavedQuery 的 GUID。请先创建视图。
  • 整个表单中的单元格、节、选项卡和控件 ID 都必须唯一。
  • 控件的 classid 值——参见上方的 classid 表格。

提示: 在 maker portal 中创建表单,然后通过 pac solution export 拉取——将拉取的 XML 用作程序化创建的模板。


创建列后:报告逻辑名称

创建列后(通过 Web API 或 MCP),务必向用户报告实际的逻辑名称。列名称可能会以用户意料之外的方式规范化或添加前缀。请用表格汇总:

显示名称逻辑名称类型
Emailcr9ac_emailString
Tiercr9ac_tierPicklist
Customercr9ac_customeridLookup

这样可以避免用户使用错误的列名称插入数据,导致后续操作失败。


常见 Web API 错误代码

错误代码含义恢复方法
0x80040216暂时性元数据缓存错误。列或表的元数据尚未传播完成。等待 3-5 秒后重试。通常第二次尝试即可成功。
0x80048d19负载中包含无效属性。字段名称与表中的任何列都不匹配。检查逻辑列名称——使用 EntityDefinitions(LogicalName='...')/Attributes 进行验证。
0x80040237架构名称已存在。创建新列或新表之前,先确认该列或表是否已存在——它可能是先前超时的调用创建的。
0x8004431a发布者前缀不匹配。确保所有架构名称都使用解决方案的发布者前缀。
0x80060891创建表后元数据缓存尚未就绪。先调用 GET EntityDefinitions(LogicalName='...') 以强制刷新缓存,然后重试。

向用户显示错误代码之前,务必将其转换为通俗易懂的说明。


元数据传播延迟与锁争用

创建表/列/备用键后,Dataverse 会运行内部元数据操作(索引构建、缓存传播),耗时 3–30 秒。在这些操作运行期间提交其他元数据操作会导致锁争用错误。

缓解方法——分阶段创建,不要交错执行。 创建所有表 → 等待 15–30 秒 → 创建所有备用键 → 等待 15–30 秒 → 创建所有查找列。不要交错执行同一张表上的操作。

症状(出现以下任一情况都意味着传播尚未完成):

  • 创建选项集列时出现 0x80040216 错误
  • Lookup @odata.bind 出现 "Invalid property" 错误
  • update_table(MCP)出现 "EntityId not found in MetadataCache" 错误
  • 创建 Lookup 或备用键时出现 "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。
  • 索引创建是异步的——对于大型表,请轮询 client.tables.get_alternate_keys(table),直到 status == "Active" 后再使用。
  • 限制:每个表最多 16 列/900 字节/10 个键;有效类型包括 Integer / Decimal / String / DateTime / Lookup / OptionSet。

有关 SDK 代码示例(单列键、复合键、幂等创建及状态检查)、Agent 选择列的决策规则(数据库源与 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 添加。

评论

支持 Markdown