关于 dv-data
dv-data 是用于在 Dataverse 中创建、更新、删除和导入记录的技能。它根据操作规模与需求,指导构建者选择 MCP、Dataverse CLI 或官方 Python SDK。
主要功能
- 01创建、更新和删除记录
- 02使用备用键进行 Upsert
- 03批量记录操作
- 04CSV 导入与查找解析
- 05文件列上传
适用场景
- 01交互式写入少量记录
- 02导入带有关联记录查找的 CSV 数据
- 03执行可重复的批量 Upsert
- 04将文件上传至 Dataverse 列
MCP 相关
对于少量交互式写入,该技能建议使用 Dataverse MCP 工具,例如 create_record、update_record 和 delete_record;每次调用最多处理 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。 不要使用 Node.js、JavaScript 或任何其他语言编写 Dataverse 脚本。如果你准备运行
npm install或编写.js文件,请立即停止——你偏离了正确方向。请参阅概览技能中的硬性规则。
ERP 例外(包括仅规划的请求): 加载 dv-overview,然后阅读 references/erp-writes.md。ERP 写入和 DMF 导入不使用下文的 Dataverse SDK 指南。即使请求不执行操作,仍可加载技能并阅读本地参考资料。
所有数据写入操作均使用 Microsoft 官方 Power Platform Dataverse Client 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),对于少量交互式写入,这是最快捷的方式——每次调用最多处理 25 条记录,无需编写脚本。Dataverse CLI(dataverse data create/update/upsert/delete)可通过无头单行命令处理单条记录写入、关联/取消关联和文件上传。SDK 是处理超过 25 条记录的批量写入、数据转换、重试逻辑、CSV 导入或仅 SDK 支持的操作(使用备用键进行 upsert——MCP 没有 upsert 工具)的默认选择。根据工作量和数据特征选择合适的方式。
使用脚本执行写入时,使用 SDK,而不是手动编写 HTTP 请求
MCP/CLI/SDK 的选择取决于功能(如上文所述;另请参阅概览中的工具功能 / 硬性规则 2)。本节范围更窄:一旦决定通过脚本执行写入,对于 SDK“支持”列表中的操作,应使用 SDK,而不是手动编写 urllib/requests——SDK 已处理身份验证、分页和重试,无需重新实现这些功能。对于 SDK 未涵盖的少数操作,请使用 dataverse api 备用方式——不要手动编写 urllib。
正确导入(完整脚本中始终先执行 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。对于以下真正不支持的功能,优先使用托管的 dataverse api 作为替代方案,而不是直接使用 urllib:
- 全局选项集——参见 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 每次调用最多批量处理 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 会失败并报错“An unexpected error occurred”(单条 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 批量导入
对于可能会重新运行的导入(大多数实际场景),请使用带备用键的
UpsertItem,而不要使用create()— 参见references/multi-table-fk-import.md。这里的create()模式仅适用于一次性加载。
| 规模 | 工具 | 原因 |
|---|---|---|
| 1 条记录 | CLI dataverse data create 或 MCP create_record | 无需脚本 |
| 2–25 条记录 | MCP create_record | 每次调用最多批处理 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)中批量创建记录之前:
- 使用计划中的最小有效负载创建一条测试记录
- 如果引发
HttpError400,错误消息会指出缺少的必填字段 - 某些必填字段由插件强制要求,在
describe中不可见 - 删除测试记录,然后继续批量创建
带外键依赖的多表导入
在多个存在外键关系的表之间导入数据时,必须按依赖顺序运行导入,并使用 UpsertItem + 备用键(幂等,可安全重新运行)。
快速参考:
- 创建包含源 ID 列、备用键和查找关系的表(参见 dv-metadata)。
- 使用
ThreadPoolExecutor并行导入 Level 0(无外键依赖)表。每个表内的分块按顺序处理(并发写入会造成死锁)。 - 通过查询回填源 ID → GUID 映射(upsert 不会返回 GUID)。
- 对每个依赖级别重复上述操作 — Level 1 需要 Level 0 的映射来设置
@odata.bind。
完整模式 — 自适应 bulk_upsert 助手、复合键处理、导入后验证以及首次使用的 bulk_create 变体 — 请参见 references/multi-table-fk-import.md。
关键约束(即使不阅读参考文档也适用):
- 在同一层级的表之间并行处理,层级之间按顺序处理,每个表内的分块按顺序处理。
- 备用键列不得同时出现在记录正文中 —
UpsertMultiple会失败。 - 在执行器中捕获每个表的失败 — 一个表失败不得导致其他表停止。
- 从
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()),不要使用 shell 反引号替换。
示例数据生成
内联生成逼真的示例记录——由架构驱动、不依赖特定表,并采用保护 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电话。确认后继续,或指定其他环境。”
为自定义实体生成示例数据(架构未知——用文字说明即可):
- ❌“我需要了解更多实体信息。有哪些必填字段?”
- ✅“自定义实体——我将查询
EntityDefinitions,发现cr123_project的必填列,然后内联生成 5 条记录,根据AttributeType将每列映射到一个生成器,并调用client.records.create(\"cr123_project\", records)。确认后继续,或告知我其他数量。”