关于 dv-solution
dv-solution 是面向 Dataverse 开发者的解决方案管理技能,使用 PAC CLI 与 Python SDK 创建、导出、打包、导入并验证解决方案。它涵盖从源文件往返到环境部署后的组件检查。
主要功能
- 01创建解决方案及添加组件
- 02导出与解包解决方案
- 03打包并导入解决方案
- 04导入后验证组件和状态
适用场景
- 01将环境中的解决方案拉取为可编辑文件
- 02打包源文件并导入目标环境
- 03确认导入后表、表单和视图可用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ dataverse-skills --skill dv-solution安装后在 Claude Code 中输入/dv-solution确认已加载。
详细介绍
技能:解决方案
通过 PAC CLI 创建、导出、解包、打包、导入和验证 Dataverse 解决方案。包含使用 Python SDK 进行的导入后验证。
无头/受限出站流量的主机:在线步骤请使用原始 Web API(
ExportSolution/ImportSolution)。pac solution pack/unpack是本地文件操作(无需身份验证),但需要可运行 PAC 的主机——请在具备相应能力的计算机或 CI 运行器上执行。使用python scripts/auth.py --check验证出站流量。请参阅dv-connect/references/headless-hosts.md。
技能边界
| 需求 | 改用 |
|---|---|
| 创建表、列、关系、表单、视图 | dv-metadata |
| 创建、更新或删除数据记录 | dv-data |
| 查询或读取记录 | dv-query |
| 连接到 Dataverse/设置 MCP | dv-connect |
创建新解决方案
使用 Python SDK 创建发布者和解决方案记录,不要使用原始 HTTP。 发布者和解决方案是标准的 Dataverse 表。client.records.create() 和 client.records.list() 会自动处理身份验证、分页和错误处理,避免原始 urllib 调用引入的 URL 编码、请求头样板代码和 GUID 解析错误。
步骤 1:查找或创建发布者
每个解决方案都属于一个发布者。发布者的 customizationprefix(例如 contoso、sa、lit)会加在每个自定义表、列和关系的架构名称前。这个前缀实际上是永久性的——即使之后更改发布者,现有组件也会永久保留其前缀。
切勿使用默认的 new 前缀。 它无法体现组织身份,会带来命名冲突风险,也表明开发者没有遵循最佳实践。
发现流程——创建发布者之前务必先运行:
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-solution")
# 1. Query for existing non-Microsoft publishers
publishers = client.records.list(
"publisher",
filter="customizationprefix ne 'none' and uniquename ne 'MicrosoftCorporation' and uniquename ne 'Microsoftdynamic'",
select=["publisherid", "uniquename", "friendlyname", "customizationprefix"],
top=10,
)
if publishers:
# Show existing publishers and ask user which to use
print("Existing publishers in this environment:")
for p in publishers:
print(f" {p['uniquename']} (prefix: {p['customizationprefix']}_)")
# ASK THE USER: "Which publisher should this solution use?"
# Or: "Should I reuse '<name>' (prefix: <prefix>_)?"
publisher_id = publishers[0]["publisherid"] # after user confirms
else:
# No custom publisher exists — ASK THE USER for prefix
# "What publisher prefix should I use? (e.g., 'contoso', 'sa', 'lit' — 2-8 lowercase chars)"
publisher_id = client.records.create("publisher", {
"uniquename": "<publisheruniquename>",
"friendlyname": "<Publisher Display Name>",
"customizationprefix": "<prefix>", # from user input, NOT 'new'
"description": "<description>",
})
规则:
- 创建新发布者或选择前缀之前,务必先询问用户。切勿将前缀硬编码。
- 前缀必须与解决方案中已创建的任何表一致——不能混用前缀。
- 一个发布者可以拥有多个解决方案。尽可能重复使用现有发布者。
步骤 2:创建解决方案记录
使用 SDK 创建解决方案记录(优先于原始 Web API):
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-solution")
# Create the solution record
solution_id = client.records.create("solution", {
"uniquename": "<UniqueName>",
"friendlyname": "<Display Name>",
"version": "1.0.0.0",
"[email protected]": "/publishers(<publisher_guid>)",
})
print(f"Created solution: {solution_id}")
必填字段:
Table: solution
Fields: uniquename = "<UniqueName>"
friendlyname = "<Display Name>"
version = "1.0.0.0"
publisherid = <publisher GUID from step 1>
**注意:**没有
pac solution create命令。PAC CLI 用于导出/导入/打包/解包,而不是创建解决方案记录。请使用 SDK 或 Web API 创建记录。
步骤 3:添加组件
使用 pac solution add-solution-component 添加表、表单、视图和其他组件:
pac solution add-solution-component \
--solutionUniqueName <UniqueName> \
--component <ComponentSchemaName> \
--componentType <TypeCode> \
--environment <url>
**注意:**PAC CLI 在此处使用 camelCase 参数(
--solutionUniqueName、--componentType),而不是 kebab-case。
常见组件类型代码:
| 类型代码 | 组件 |
|---|---|
| 1 | Entity(表) |
| 2 | Attribute(列) |
| 26 | 视图 |
| 60 | 表单 |
| 61 | Web 资源 |
| 300 | Canvas 应用 |
| 371 | 连接器 |
对要添加的每个组件重复运行此命令。
替代方法:通过 MSCRM.SolutionName 请求头自动添加
通过 Web API 创建元数据时,请包含 MSCRM.SolutionName 请求头,以便自动将组件添加到解决方案:
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"MSCRM.SolutionName": "<UniqueName>"
}
**重要:**使用此方法后,请通过 SDK 查询 solutioncomponent 表,确认组件已添加(当前 PAC 中没有 pac solution list-components 命令):
sol = client.records.list("solution",
filter="uniquename eq '<UniqueName>'", 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")
如果请求头拼写错误或解决方案不存在,组件将被静默创建到默认解决方案中。务必进行验证。
查找解决方案名称
导出前,请确认准确的唯一名称:
pac solution list --environment <url>
UniqueName 列是传递给其他命令的名称。显示名称包含空格;唯一名称不包含空格。
拉取:导出 + 解包
导出或导入前,请确认目标环境。 运行
pac auth list+pac org who,向用户展示输出,并确认与预期环境一致。开发者会在多个环境中工作——不要想当然。
将解决方案导出为非托管格式(事实来源):
pac solution export \
--name <UniqueName> \
--path ./solutions/<UniqueName>.zip \
--managed false \
--environment <url>
解包为可编辑的源文件:
pac solution unpack \
--zipfile ./solutions/<UniqueName>.zip \
--folder ./solutions/<UniqueName> \
--packagetype Unmanaged
Windows 文件锁竞争。 请像上面那样将导出和解包作为单独命令运行;紧接着串联运行可能会遇到导出后 ZIP 文件短暂被锁定的问题。如果
unpack因锁定/“正在使用”错误而失败,请稍等片刻后重试,并在删除 zip 前确认解包后的文件夹包含预期组件。
删除 zip——解包后的文件夹才是源:
rm ./solutions/<UniqueName>.zip
提交:
git add ./solutions/<UniqueName>
git commit -m "chore: pull <UniqueName> baseline"
git push
推送:打包 + 导入
将源文件重新打包为 zip:
pac solution pack \
--zipfile ./solutions/<UniqueName>.zip \
--folder ./solutions/<UniqueName> \
--packagetype Unmanaged
导入(大型解决方案建议使用异步方式):
pac solution import \
--path ./solutions/<UniqueName>.zip \
--environment <url> \
--async \
--activate-plugins
轮询导入状态
异步导入后,检查作业:
pac solution list --environment <url>
导入后验证
导入解决方案后,请确认组件已上线。使用 Python SDK 直接检查——无需外部脚本。
检查表是否存在
info = client.tables.get("<logical_name>")
if info:
print(f"[PASS] Table '{info.logical_name}' exists")
else:
print(f"[FAIL] Table '<logical_name>' not found")
检查表单是否已发布
forms = client.records.list(
"systemform",
filter="objecttypecode eq '<entity>' and type eq <form_type_code>",
select=["name", "formid"],
top=5,
)
# Form type codes: 2 = main, 7 = quick create
检查视图是否存在
views = client.records.list(
"savedquery",
filter="returnedtypecode eq '<entity>'",
select=["name", "savedqueryid", "statuscode"],
top=10,
)
检查用户的角色分配(N:N $expand)
records.list 会直接传递 $expand,因此可使用 SDK 直接读取 N:N 导航属性:
users = list(client.records.list(
"systemuser",
filter="internalemailaddress eq '<email>'", # fallback: domainname eq '<upn>'
select=["fullname"],
expand=["systemuserroles_association($select=name)"],
top=1,
))
roles = [r["name"] for r in users[0].get("systemuserroles_association", [])] if users else []
或者使用托管的 Dataverse CLI 备用方式(dataverse api request——而非 urllib),或使用带有 link-entity 的 FetchXML:
dataverse api request --target dataverse --method GET \
--path "/api/data/v9.2/systemusers?%24filter=internalemailaddress eq '<email>'&%24select=fullname&%24expand=systemuserroles_association(%24select=name)&%24top=1" \
--environment <DATAVERSE_URL> \
--context "app=dataverse-skills/<ver>;skill=dv-solution;agent=<agent>"
响应中的 value[0].systemuserroles_association 是已分配角色的列表(每个角色都有 name)。
检查导入错误
jobs = client.records.list(
"importjob",
select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
orderby=["startedon desc"],
top=5,
)
如需查看详细错误历史记录,也可查询 msdyn_solutionhistory:
history = client.records.list(
"msdyn_solutionhistory",
filter="msdyn_status eq 1", # 1 = failed
select=["msdyn_name", "msdyn_starttime", "msdyn_exceptionmessage"],
orderby=["msdyn_starttime desc"],
top=5,
)
验证错误参考
| 错误 | 原因 | 修复方法 |
|---|---|---|
| 导入后找不到表 | 组件不在解决方案中 | 使用 pac solution add-solution-component 添加 |
| 表单检查立即失败 | 发布是异步的 | 等待 30 秒后重试 |
| 未分配角色 | 用户尚未配置 | 使用 pac admin assign-user 或 Power Platform 管理中心分配角色 |
| 导入作业进度为 0% | 导入仍在进行 | 60 秒后再次轮询 |
备注
- 开发解决方案始终使用
--managed false/--packagetype Unmanaged。托管包用于部署到下游环境(测试、生产)。 --activate-plugins可确保解决方案中已注册的插件在导入时激活。- 如果看到“solution already exists”错误,请使用
--import-mode ForceUpgrade覆盖。 - 大型解决方案(Sales、Customer Service)可能需要 10–20 分钟才能导入。请耐心等待并轮询,不要重新导入。
- 上述所有验证查询都需要身份验证。使用
scripts/auth.py获取凭据/令牌。有关 SDK 查询模式,请参阅dv-query;有关写入模式,请参阅dv-data。