mcpField
A
add-sample-data

add-sample-data

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

关于 add-sample-data

add-sample-data 是面向 Power Pages 代码应用的 Dataverse 数据填充技能,供已创建数据表的项目使用。它按已批准的表范围和依赖顺序生成情境化记录,并支持从原型种子数据复用。

主要功能

  1. 01按批准范围选择数据表
  2. 02按依赖顺序插入记录
  3. 03生成符合场景的样例数据
  4. 04支持原型种子数据复用
  5. 05跳过已达记录目标的表
  6. 06从部分完成状态安全重跑

适用场景

  1. 01为新建 Dataverse 表填充演示数据
  2. 02将原型种子数据映射到 Dataverse
  3. 03在部分插入失败后继续填充

安装

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

$npx skills add https://github.com/microsoft/power-platform-skills --skill add-sample-data

安装后在 Claude Code 中输入/add-sample-data确认已加载。

详细介绍

插件检查:运行 node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出消息,请先向用户显示,然后再继续。

📋 共享说明:shared-instructions.md — 请先阅读。

添加示例数据

应用根目录: 在读取任何项目内容或执行任何命令之前,先执行 app-working-directory.md。 所有 shell 调用和文件工具(包括媒体路径和重试)都使用解析出的绝对路径 working_dir; 切勿沿用之前的 cd。

向 Dataverse 表填充真实感强的示例记录,让刚搭建完成的代码应用在首次启动时就能显示看起来真实的数据。根据每个表的架构生成行,并按依赖顺序插入。请在 /add-dataverse(或 /setup-datamodel)创建表后使用。

核心原则

  • 覆盖已批准范围内的表,而非整个清单。 为每个符合条件的已批准表填充少量实用数据。清单是经过验证的应用库存,可能包含即将退役或不相关的表;某个表出现在清单中,并不代表可以为其填充数据。下文所有覆盖、扇出、原型复用、媒体和重试规则都不能凌驾于范围限制和退役排除规则之上。
  • 插入顺序很重要。 必须先插入父表/被引用表,再插入子表/引用表,这样才能获取查找 ID。
  • 使用符合上下文的数据,而非 Lorem Ipsum。 生成与列名和类型相符的值。在巡检应用中,cr3e9_sitename 列应填入“Westside Construction Site”,而不是“Sample Name 1”。
  • 生成符合场景的行。 如果存在 native-app-plan.md,请阅读它,尤其是 ### Shared Conventions 和 screen-templates.md 中定义的各屏幕 Operational pattern 值;否则,请使用当前数据请求和经过验证的架构。填充的行应能体现应用的实际工作流:状态、日期、关系、优先级/严重程度、媒体元数据,以及能让计划中的首屏内容丰富起来的边缘情况。
  • 优雅处理失败。 插入失败时,记录错误并继续处理剩余记录——绝不自动回滚。用户修复问题后可以重新运行。
  • 重新运行时保持幂等。 如果之前的运行只完成了部分操作,第二次运行时读取 memory-bank.md 的已填充数据表,并跳过已插入的记录。
  • 插入操作限定在解决方案内。 始终传入 --solution <uniqueName>,确保记录写入我们的解决方案,而非默认解决方案。

工作流

  1. 确定已批准的数据填充范围 → 1. 验证项目和身份验证 → 2. 发现范围内的表 → 3. 选择表和数量 → 4. 生成并预览 → 5. 插入 → 6. 汇总
步骤 0 — 确定已批准的种子范围

输入是技能参数,而不是 dataverse-request.js 的标志:

  • --working-dir <absolute-root>:继承调用方的根目录;拒绝冲突的显式/继承根目录。在嵌套调用中,绝不默认使用 shell 当前工作目录。
  • --tables <comma-separated-logical-names>:确切的已批准种子表允许列表。每次编排调用都必须提供,包括全新创建。
  • --exclude-tables <comma-separated-logical-names>:已批准编辑中要退役的表名;仅当没有表要退役时,才省略此参数或传入显式空字符串。

对于编排调用,在进行身份验证、记录数查询或生成之前,必须要求提供范围受限的交接信息以及匹配的 --tables。缺少允许列表或允许列表格式错误时,返回 NEEDS_CONTEXT;绝不回退到清单中的所有表。显式空允许列表表示不执行任何操作:报告没有已批准的种子表,并在不调用云服务或写入数据的情况下返回。

根据清单以及已批准计划/调用方范围验证逻辑名称。根据调用方的退役集合以及当前计划/历史记录中的任何待处理移除项构建 retiringTables;将这些表包含在 --exclude-tables 中。如果允许列表中的名称不存在/未经验证,或与 retiringTables 重叠,则在插入前停止并返回范围不匹配。不要静默丢弃未知名称,也不要从过渡中的清单条目推断许可。

对于独立请求,遵循显式指定的 --tables 和排除项。如果未提供表列表,则在步骤 2 中发现候选表,并在步骤 5 之前请用户批准具体的表集合/数量,然后将该选择固定为 seedTables。对于此独立发现路径,如果本地清单不存在,则根据已验证的元数据进行验证;绝不要将发现结果本身视为批准。待处理的退役项始终会排除候选表;如果状态不明确,应询问,而不是将清单视为批准。只读回退发现不代表获准向环境中填充种子数据。

在整个调用过程中保持固定的 seedTables 允许列表。之后所有选择、批次、媒体任务和恢复操作都必须限定在此范围内。不要修改 .datamodel-manifest.json 或应用计划来使被排除的表符合条件。要扩大范围,必须返回所属的审批关卡。

对于 --plan-only 或规划阶段交接,使用提供的拟议表范围进行只读检查,并在步骤 5 之前返回拟议的数量、依赖需求和媒体策略。规划批准不等于插入批准。此路径不得生成媒体文件、插入/更新/上传记录,也不得更改应用计划、清单或种子数据历史记录。

原型种子数据复用

--from-seed 由 /prototype-to-real-app 在将模拟原型转换为 Dataverse 后使用。在此模式下,优先复用现有原型种子文件,而不是生成新行:

src/generated/services/*/*.seed.json
src/generated/services/*.seed.json

仅使用 .datamodel-manifest.json 将 seedTables 对象映射到 Dataverse 负载;原型种子文件不得扩大已批准范围:

  • 仅保留真实清单列的值。
  • 将查找引用转换为清单中的精确 <schemaName>@odata.bind 键。
  • 保留清单中的选项集整数;不要根据标签臆造值。
  • 跳过没有 Dataverse 列的仅限本地原型字段。
  • 保持依赖层级插入顺序。

如果无法安全映射种子文件,则回退到为该表生成符合上下文的示例行,并在摘要中记录 DONE_WITH_CONCERNS。--from-seed 仅表示优先使用种子文件,并不代表获准插入格式错误的数据。


步骤 1 — 验证项目与身份验证

在每次 Shell 调用中,从解析出的 working_dir 运行应用本地命令。

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
if [ ! -f power.config.json ] || [ ! -f app.config.js ]; then
  echo "BLOCKED: working_dir is not an initialized app" >&2
  exit 1
fi
environment_id="$(node -p "require('./power.config.json').environmentId || ''")" || exit 1
if [ -z "$environment_id" ]; then
  printf '%s\n' 'ERROR: selected app has no environmentId' >&2
  exit 1
fi
node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$environment_id" --no-cache --require-tenant

记录环境 URL、环境 ID和租户 ID,供后续脚本调用使用;这些信息必须与所选应用及任何提供的所有者上下文匹配。经过有限次数的只读恢复后,身份信息仍不完整或相互冲突时,返回 NEEDS_CONTEXT。不得移除安全标志、选择其他环境或保存解析器输出以进行恢复。

将后续每次 Dataverse 读取、批处理、PATCH、媒体上传、令牌刷新和重试都绑定到该确切的环境 URL 和租户 ID。对于每次 dataverse-request.js 调用,都要显式传入 --tenant-id '<tenantId-from-resolve-environment>';在每次新的 Shell 调用中重新使用经过验证的步骤 1 值。不得依赖之前的 Shell 导出、质询发现或当前 Azure CLI 租户。租户上下文缺失或冲突时,在调用前返回 NEEDS_CONTEXT。

验证 Azure CLI 身份验证(脚本需要 Azure CLI 令牌):

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
az account show --query "user.name" -o tsv

如果为空,提示运行 az login 并停止。

步骤 2 — 发现表

遥测检查点:discover_dataverse_tables

步骤 2a — 路径 A:读取 .datamodel-manifest.json(首选)
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
test -f .datamodel-manifest.json

如果存在,则解析 JSON。它包含经过验证的应用清单中的 logicalName、displayName、status(new / extended / reused)和 columns。 这是首选路径——速度快,且无需 API 调用。对于范围限定的调用,只能在执行任何逐表元数据或计数查询之前,使用经过验证的 seedTables 条目。对于独立发现,从候选列表中排除待退役表。

将无关的清单条目仅作为清单保留;不得将其传入记录计数、生成或插入循环。

跳过步骤 2b。

步骤 2b — 路径 B:查询 OData(回退方案)

如果在编排调用中缺少 .datamodel-manifest.json,则向所有者返回 BLOCKED,要求恢复经过验证的清单;不得扩大发现范围或臆造架构信息。对于带有明确允许列表的独立请求,只获取这些逻辑名称的元数据。以下广泛发现仅用于不带表列表的独立请求,以便提出候选项供审批:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "EntityDefinitions?\$select=LogicalName,DisplayName,EntitySetName&\$filter=IsCustomEntity eq true" \
  --tenant-id '<tenantId-from-resolve-environment>'

对于项目使用的每个候选表(或每个明确限定范围的表),获取其自定义列:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "EntityDefinitions(LogicalName='<table>')/Attributes?\$select=LogicalName,DisplayName,AttributeType,RequiredLevel&\$filter=IsCustomAttribute eq true" \
  --tenant-id '<tenantId-from-resolve-environment>'

构建与清单提供的 { logicalName, displayName, columns: [...] } 相同的结构。

第 3 步 — 选择表格 + 计数

仅评估经过验证的种子允许列表;仅当复用的表格明确位于该列表中时,才将其纳入评估。对于独立发现,只使用建议保留的候选表格,并在插入前获得选择批准。 未经下方额外的共享环境明确确认,绝不为标准系统表(例如 contact、account、systemuser)生成种子数据。

预生成种子数据前的行数检查(严格要求 — 每张表生成任何行之前都要执行):

对于每张范围内的表,使用清单中的已验证实体集名称查询当前记录数,或按照第 5a 步从元数据中获取该名称。 绝不能通过在逻辑名称后追加 s 来推导该名称。

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "<entitySetName>?\$top=5&\$select=<primaryKeyColumn>" \
  --tenant-id '<tenantId-from-resolve-environment>'

统计 value 数组中返回的行数。

现有记录数操作
≥5完全跳过此表。 记录:↷ <table> (≥5 records exist, skipping)。不要生成或插入任何行。
<5生成足够的新行,以达到每类目标数量。如果已有部分记录(例如 2 条),只生成差额(例如再生成 3 条,达到 5 条)。

如果所有符合条件的已选表格都已有 ≥5 条记录,则报告已批准的种子范围内无需生成任何种子数据,并停止。

按类别设置每张表的记录数(生成数据前,依据清单信号对每张表进行分类;这优于统一设为 5,因为参考表不需要大量数据,而事务表需要覆盖不同状态):

类别启发式判断默认数量原因
参考表没有 status / state 列;列为描述性字段(名称、地址、电话)。通常为第 0 层。3门店、客户、站点、产品。数量少且稳定。
联结表有两个或更多查找列,没有其他有意义的列。通常为第 1 层。每个父记录 3 条,最多 9 条Store-Assignment、Project-User。需要覆盖联结关系。
事务表有 status / state / phase 选项列,以及日期列(createdon、submittedat、completedat)。5审计、检查、订单、工单。需要混合状态,以便磁贴显示内容。
明细 / 行项目表有指向事务父记录的查找列,没有自身的状态列。每个父记录 2-3 条审计区域、订单行项目、检查发现。
问题 / 发现表有指向事务父记录的查找列 + status(Open / Resolved)和严重程度(Critical / Moderate / Minor)。每个事务父记录 3-4 条问题、缺陷、观察项。混合严重程度和状态(见第 4a 步)。
证据 / 附件表有 File / Image 列 + 指向问题 / 检查记录的查找列。约每 30% 的父记录 1 条照片证据、文档上传。默认生成元数据行;仅当 brand/media-policy.md 或用户请求表明需要示例媒体时,才生成文件 / 图像字节。
日志 / 事件 / 审计跟踪表仅追加,包含 eventtype 枚举 + 时间戳 + 操作者查找列。每个事务父记录 2 条审计日志事件、活动流。每个父记录至少混合 2 种事件类型。
覆盖 / 审批表有指向事务父记录的查找列 + 状态(Pending / Approved / Rejected)。约每 20% 的父记录 1 条覆盖请求、审批队列。至少添加 1 条 Pending 记录,让队列选项卡显示内容。

数量刻意保持精简。目标是让每个屏幕都能显示一些内容,而不是创建演示数据集。

打印一行摘要并继续:

→ Seeding <total> records into <N> tables (coverage-first; counts auto-tuned per class).

步骤 3b — 确定插入顺序

对于选定的表,根据查找列构建依赖图:

  1. 没有指向其他表的查找列 → 第 0 层(最先插入)
  2. 仅查找第 0 层表的表 → 第 1 层
  3. 继续分层,直到所有选定的表都已分层

如果选定的表引用了未选定的父表,则通过有界读取复用已验证的现有父记录,前提是该查找在已批准的功能范围内。读取查找父记录并不意味着可以插入或更新该记录。如果没有合适的记录,则向负责人返回 NEEDS_CONTEXT(或在独立执行时询问),以扩展种子范围或明确省略可选查找。绝不可自动将父表加入 seedTables、创建即将停用的父记录,或省略必需查找。指向即将停用目标的查找会阻止相关种子数据,直到计划得到协调。

步骤 4 — 生成示例数据 + 预览

遥测检查点:generate_and_review_sample_records

步骤 4a — 生成符合上下文的行

为每个选定的表生成 N 行。根据列名和类型匹配值:

列类型生成方式
String根据列名的语义匹配。*name、*title → 根据需求简介上下文生成真实感姓名。*email → [email protected]。*phone → (555) 123-NNNN。*address → 真实感街道 + 城市。其他情况:简短且符合上下文的文本。
Memo(多行文本)生成 1-3 个与列名相关的句子(例如 *notes、*description)。
Integer / Decimal / Currency根据列名选择合理范围。*amount、*price → 真实感美元金额。*count、*quantity → 小整数(1-100)。
DateTime / DateOnly生成近期 ISO 日期,范围为过去 30 天至未来 14 天。各行日期应有所不同。
Boolean混合生成 true/false(约 70/30;对于 is_active 这类名称,优先生成 true)。
Choice (Picklist)先查询选项(步骤 4b),然后从有效整数值中选择。
MultiSelect Choice每行从选项集里选择 1-3 个有效值。
Lookup引用已验证的现有父记录,或在已批准的种子范围内插入的父记录。跟踪线上父记录 GUID;绝不可仅为满足子记录而播种未经批准的父记录。
Image / File默认:跳过,留空。如果启用了媒体播种,且该列存储业务数据(产品图片、检查证据、NC 证明),则使用 assets/sample-* 中生成的/合成的本地文件,并记录来源。绝不可将装饰性的 UI 主视觉素材上传到 Dataverse。

媒体播种策略(仅限业务数据,且仅在需要时):

  • 默认情况下,不播种二进制媒体。播种元数据行,并将 Image/File 列留空,除非屏幕计划或用户请求要求提供可见的示例媒体。
  • 仅当图片属于用户会在列表/详情屏幕中查看的记录时,才向 Dataverse 播种图片/文件:产品照片、证据、附件、签名、问题证明。不要播种 Home 主视觉素材、启动画面、应用图标、空状态插画或装饰性的详情背景。
  • 优先使用不含徽标、真实产品标签、人脸、水印和竞争对手品牌的生成素材/合成素材。如果用户提供了已批准的素材,则使用这些素材并记录来源。
  • CDN URL 仅适用于明确的 URL/Text 列(例如 imageurl、photourl)。不要将 CDN URL 放入 File/Image 列。
  • Dataverse Image 列在行负载或生成的服务形状中接收 base64。Dataverse File 列需要在元数据行创建后再执行一次上传。
  • 对于产品/渠道应用,如果产品列表/详情屏幕需要显示图片,产品图片就是核心示例数据,但数量有限。仅生成并上传具有代表性的子集;其余记录使用本地占位图或将图片字段留空。
  • 使用 assets/images/asset-manifest.json 或 assets/sample-media/asset-manifest.json 维护文件、用途、来源/许可和安全说明。

媒体数量上限(硬性):

  • 产品/目录表:默认最多为 min(6, record count) 条记录上传图片。如果记录按类别划分,则每个类别选择 1-2 条,直到达到上限。其余记录使用本地占位缩略图或空状态回退方案。
  • 证据/附件表:最多为 30% 的父记录上传示例文件,每张表总计不超过 5 个文件。即使没有文件字节数据,元数据行仍可存在。
  • 用户/头像/设备/站点图片:每张表最多上传 3 张,除非用户明确要求更丰富的视觉演示。
  • 未经用户明确批准,在一次 /add-sample-data 运行中,生成的媒体文件总数不得超过 10 个。
  • 如果计算出的示例行数超过媒体上限,应优先保证覆盖面,而不是追求数量:一个主打产品、一个次要产品、一个边缘/状态示例,然后使用占位图。

如何插入示例图片:

  1. 先生成常规记录内容(名称、查找字段、状态等)。
  2. 对于 Image 列,只有当生成的模型/服务要求该列使用 base64 时,才在 create/PATCH 请求体中附加紧凑的 base64 数据。图片应足够小,以适合移动端缩略图。
  3. 对于 File 列,不要在 create 请求体中放入字节数据/base64/URL。先插入元数据行,从 Step 5 的 BATCH-RECORDS 结果中获取 GUID,然后在 Step 5d 中将生成的文件上传到 (recordId, columnName)。
  4. 达到媒体上限后停止。其余记录的 Image/File 列保持为 null,并在应用 UI 中使用本地占位图。
  5. 如果 File 列没有上传辅助工具,则将该列留空,并报告 sample media skipped — upload helper missing。绝不要用 URL 字符串伪造 File/Image 数据。

从需求简述中获取上下文。 用户描述了应用的用途(例如“供现场技术人员使用的 HVAC 检查应用”);应据此为数据增添特色,例如将站点命名为符合用户行业特点的街道名称,并使用恰当的状态术语。使用通用的 Lorem Ipsum 就是失败。

每个父记录的扇出下限(硬性要求)。 对于 Tier K+ 中的每张子表,除非关系明确为可选(清单中 RequiredLevel: None,且列名不暗示一对多关系,如 *audit*、*inspection*、*order*),否则每条 Tier K-1 父记录至少生成 1 行。如果没有这个下限,随机分配查找字段会导致某些父记录没有子记录,从而使父记录的详情屏幕显示为空。具体规则:如果正在生成 audit_zones,且有 5 条审计记录,则至少生成 5 个区域(每条审计记录 1 个),然后为每条审计记录再增加 0-2 个,直到达到每类记录的目标数量。绝不能反过来——绝不能只生成总计 N 条,再任由随机结果决定每行归属哪个父记录。

状态分布(对事务、问题、覆盖类为硬性要求)。 如果某张表有 status / state / phase / severity / priority 选项列,行数据必须分布在至少 2 个不同值中——不能全部为 Open,也不能全部为 InProgress。具体规则:

  • 事务(audit、inspection、order):如果选项集中有可用状态,至少混用 3 种生命周期状态——通常是 1 行处于早期状态(Draft / InProgress),1 行处于中期状态(Submitted / PendingReview),其余处于终态(Signed / Completed / Closed)。如果只有 2 种状态,则按约 60/40 的比例拆分。
  • 问题/发现项:严重程度和状态应分别混合分布。如果有 5 个问题,涉及 3 种严重程度 × 2 种状态,应力求每种严重程度至少出现 1 次,并且至少有 1 个 Open 和 1 个 Resolved。
  • 覆盖/审批:队列/收件箱标签页必须至少有 1 行处于驱动队列的状态(Pending),其余记录分布在 Approved / Rejected 中。
  • 日志/事件:每个父记录至少有 2 种不同的 eventtype 值(例如 Created + StatusChanged)。 日期分布(仅事务记录/日志)。 如果表中有 createdon / submittedat / completedat / eventtimestamp 列,请将记录分散到今天和过去 14 天(不要全部放在今天,也不要全部放在 30 天前)。分布:约 30% 在今天/昨天(用于展示“近期活动”图块),约 50% 在过去 7 天,约 20% 在 8–14 天前。参考表和详情表可以使用任意合理的日期——只有事务记录/日志需要按时间分布。

场景类型边界覆盖(可检测时必须满足)。 使用所选 Power Apps 场景类型,确保至少有一行体现应用 UI 承诺支持的每种关键状态:

  • 现场检查/审计:一条受阻或缺少证据的审计、一条进行中的审计、一条已完成/已签署的审计。
  • 资产维护:一张逾期/高优先级工单、一张等待零件的工单、一张已完成的工单。
  • 库存扫描优先:一条差异、一件零库存/低库存商品、一条已解决的盘点/调拨记录。
  • 审批:审批收件箱中有一项待处理、一项已批准、一项已拒绝/要求修改。
  • CRM:一段有风险的关系、一项即将进行的跟进、一段健康/已赢单的关系。
  • 零售目录/订单:一件带图片的商品、一件低库存/缺货商品、一张包含多个明细行的订单。
  • 案例管理:一条已升级/SLA 有风险的案例、一条等待客户回复的案例、一条已解决的案例。
  • 入职/培训:一个逾期的必修模块、一个进行中的模块、一个已完成/已认证的模块。
  • 费用/请求受理:一份缺少收据的草稿、一项待审批请求、一项已批准/已付款的请求。
  • 健康/保健/护理:一项今天到期的护理任务、一项错过/待跟进的任务、一个已完成/进展正常的目标。
步骤 4b — 探索 Choice 选项

对于所选表中的每个 Choice 列,在生成记录之前查询其选项集:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "EntityDefinitions(LogicalName='<table>')/Attributes(LogicalName='<column>')/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?\$expand=OptionSet" \
  --tenant-id '<tenantId-from-resolve-environment>'

使用响应中的实际 Value 整数。不要硬编码 100000000 这类值——它们会因环境而异。

步骤 4c — 向用户预览

对于每张表,直接在对话中用 Markdown 表格预览记录:

### Job Site (cr3e9_jobsite) — 5 records

| Site Name | Address | Square Feet | Active |
|---|---|---|---|
| Westside Construction Site | 4521 Industrial Pkwy | 12500 | true |
| Downtown Office Building   | 188 Main St           |  3200 | true |
| Eastgate Warehouse         | 9047 Logistics Way    | 28000 | false |
| Riverside Retail Plaza     | 320 River Rd          |  6800 | true |
| North Hills Distribution   | 1612 Highland Ave     | 18900 | true |

对于包含查找列的表,还要显示每条子记录引用的父记录:

cr3e9_inspection 行通过上方的 SiteName 引用 cr3e9_jobsite 记录。

步骤 4d — 继续插入

对于 --plan-only 或规划阶段的移交,请返回预览并在此处停止; 更早的批准或已填充的清单都不能越过此边界。

只有在当前限定范围内的移交或明确的独立请求中,确切的种子表/数量策略以及任何媒体写入都已获批准时,才可不经再次询问直接继续。否则,须先取得对预览的明确批准。行数用于防止重复数据量;它们不代表同意。取消后即停止。如果依赖项或记录需要扩大范围,请交还负责人处理,而不是在编排中的叶节点内批准扩大范围。

步骤 5 — 插入示例数据

遥测检查点:insert_sample_records

开始前打印:

"→ 正在按依赖层级顺序向 张表插入共 条记录(每层并行,最多同时处理 5 项)…"

按步骤 3c 中的层级顺序插入。在同一层级内,通过脚本的 BATCH-RECORDS 模式并行处理表和记录——使用单个 Node 进程,通过 Promise.all 将并发上限设为 5。不同层级之间保持串行(子记录需要从前一层级的响应中获取父记录 GUID)。

⚠️ 并行处理仅适用于记录插入。 架构操作(创建表/列/关系)必须保持串行——请参见 /add-dataverse 步骤 5 并发规则。记录不受元数据锁影响,但元数据写入会受影响。混淆两者会导致 429 / MetadataLockHeldException。

步骤 5a — 获取实体集名称

对于每张表,获取其 EntitySetName(URL 路径名称,通常为复数形式 — 例如 cr3e9_jobsites)。如果 .datamodel-manifest.json 中包含此信息,则从中读取;否则:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
  "EntityDefinitions(LogicalName='<table>')?\$select=EntitySetName" \
  --tenant-id '<tenantId-from-resolve-environment>'

缓存结果。

⚠️ 解析每个查找关系两端的 EntitySetName。 你不仅需要插入目标表的实体集名称(BATCH-RECORDS 中的 entitySet 字段),还需要 @odata.bind 值(步骤 5c)所引用的每个查找目标的实体集名称。实体集名称与逻辑名称相互独立 — Dataverse 的复数形式不规则,版本后缀也会原样保留。容易出错的示例:cr3e9_inspectionv3 → cr3e9_inspectionv3s(而不是 cr3e9_inspections);cr3e9_inquiry → cr3e9_inquiries;cr3e9_status → cr3e9_statuses。切勿将路径构造为 <logicalName>s — 始终从 EntityDefinitions 中读取。

步骤 5b — 一次插入一个层级,在层级内并行处理

对于从 0 到 N 的每个层级:

在构造或提交批次之前,重新检查每个目标逻辑名称是否在 seedTables 和 retiringTables 中。解析其确切的实体集,并且 如果任何插入/更新操作的目标是未经批准或正在退役的表,则拒绝整个拟提交批次。在执行媒体 PATCH/上传以及每次重试之前,也应用相同的检查;超出范围的操作属于范围错误,不应作为记录失败继续处理。

  1. 构建操作数组。 收集此层级所有表中的每个 (entitySet, body) 对。为每个操作标记一个唯一的 index,用于将结果映射回对应的行标识。

    [
      { "index": 0, "entitySet": "cr3e9_jobsites", "body": { "cr3e9_sitename": "Westside Construction Site", "cr3e9_address": "4521 Industrial Pkwy", "cr3e9_squarefeet": 12500, "cr3e9_active": true } },
      { "index": 1, "entitySet": "cr3e9_jobsites", "body": { "cr3e9_sitename": "Downtown Office Building", "cr3e9_address": "188 Main St",          "cr3e9_squarefeet":  3200, "cr3e9_active": true } },
      { "index": 2, "entitySet": "cr3e9_inspectors", "body": { "cr3e9_fullname": "Jane Smith", "cr3e9_email": "[email protected]" } }
      // ... all rows for tables in this tier
    ]
    

    维护侧边映射 { index → { table, rowSemantic } },以便将结果与跟踪信息对应起来。

  2. 每个层级调用一次 BATCH-RECORDS。 脚本会在内部处理并发、重试、GUID 提取和自适应节流:

    cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
    node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> BATCH-RECORDS \
      "Tier <N>" \
      --operations '<json-from-step-1>' \
      --concurrency 5 \
      --solution '<solution-uniquename-from-memory-bank>' \
      --tenant-id '<tenantId-from-resolve-environment>'
    

    输出为 { "status": 200, "data": [{ "index": 0, "status": 204, "recordId": "<guid>" }, ...] }。结果按输入索引排序(并行执行期间顺序保持稳定)。

  3. 捕获父记录 GUID。 对于响应中每个成功的行,按行标识存储 recordId。第 N+1 层级的 @odata.bind 值来自此映射。

    每插入一条记录打印一行(按输入索引顺序遍历响应数组,以便输出按顺序显示):

    → ✓ <table>: <primary-name-value> (<guid>)

  4. 处理失败。 如果层级中的任何操作返回非 2xx 状态或 error,则跳过父记录失败的子行,但继续处理下一层级,并使用已成功的行。原因:中止第 1 层会导致第 2 层及后续层级为空,无法满足覆盖范围约定。在步骤 6 的摘要中记录每个跳过的子行及其对后续层级的影响。

Step 5c — 使用查找列插入子表(也通过 BATCH-RECORDS)

对于 Tier 1 及以上的表,使用 @odata.bind 引用上一层级中捕获的父记录 GUID,构造每行的正文。BATCH-RECORDS 调用格式相同:

[
  {
    "index": 0,
    "entitySet": "cr3e9_inspections",
    "body": {
      "cr3e9_inspectionnumber": "INS-001",
      "cr3e9_status": 100000000,
      "[email protected]": "/cr3e9_jobsites(<parent-guid-from-tier-0>)"
    }
  }
]

然后:

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> BATCH-RECORDS \
  "Tier 1" \
  --operations '<json-with-bound-lookups>' \
  --concurrency 5 \
  --solution '<solution-uniquename-from-memory-bank>' \
  --tenant-id '<tenantId-from-resolve-environment>'

⚠️ 查找列写入语法(严格要求 — 格式错误时可能会静默保存为 null,也可能直接返回 404):

  • @odata.bind 左侧的属性名称必须是 PascalCase 导航属性,例如 cr3e9_Site(而非只读的 _cr3e9_siteid_value)。
  • 值是实体集路径:/<entitySetName>(<guid>)。注意前导的 /。
  • <entitySetName> 是通过 Step 5a 获取的查找目标表的 EntitySetName,而不是在列的逻辑名称后附加 s。 将其构造为 <logicalName>s 会返回 Entity '<targetTable>' With Id = <guid> Does Not Exist,即使记录确实存在也是如此,因为 Dataverse 无法将错误的实体集解析到正确的表。带版本号的表(cr3e9_inspectionv3 → cr3e9_inspectionv3s)和不规则复数形式(cr3e9_inquiry → cr3e9_inquiries)是常见陷阱。
  • <guid> 必须来自当前进程中的实时查询——可以来自父记录的 BATCH-RECORDS 响应(Step 5b/5c),也可以在恢复操作时通过对父表发起新的 GET 请求获取(Step 5f)。切勿复制粘贴先前会话标准输出中的 GUID。
  • 格式错误要么会静默丢弃关联关系(记录在没有父级 FK 的情况下写入,应用中显示为孤立记录),要么导致整个层级返回 404。规范格式请参阅 references/dataverse-reference.md § Setting Lookups。
Step 5d — 插入记录后上传示例媒体(仅在启用时)

仅当 Step 4a 为业务 Image/File 列创建了媒体任务时才运行此步骤。切勿将其用于装饰性 UI 资源。

每个媒体任务的目标表仍须位于 seedTables 中,且不在 retiringTables 中;先前捕获的 GUID 不代表可以将其用作新目标。

生成行时构建一个 mediaJobs sidecar:

[
  {
    "table": "cr3e9_product",
    "rowIndex": 2,
    "recordId": "<filled-after-BATCH-RECORDS>",
    "columnName": "cr3e9_productimage",
    "columnType": "Image",
    "filePath": "assets/sample-media/gatorade-aseptic.png",
    "fileName": "gatorade-aseptic.png",
    "purpose": "product photo"
  }
]

每个层级的 BATCH-RECORDS 响应返回后,从内存中的 { index → recordId } 映射填入 recordId,然后按列类型上传媒体:

  • 对每个媒体辅助程序(包括其身份验证和令牌刷新流程),都明确传入 Step 1 的环境 URL 和租户 ID。通过 dataverse-request.js 发出的 Image PATCH 请求也必须使用相同的 --tenant-id 参数。 如果 File 上传辅助程序无法确保绑定到该租户,则不要调用它,也不要回退到环境凭据:保留未设置的字节,报告 sample media skipped — tenant-bound upload helper missing,并返回保留元数据行的媒体问题。
  • **Image 列:**如果创建正文中已包含 base64 值,则将媒体任务标记为已完成。否则,使用实体集和记录 GUID 将 base64 字符串 PATCH 到该记录。图像应保持较小且适用于应用;切勿将数 MB 的原始照片上传为种子数据。
  • **File 列:**记录创建后调用受支持的上传辅助程序。在应用代码中,生成的服务形式为 Service.upload(recordId, columnName, file, fileDisplayName?);对于 CLI 种子数据填充,请使用执行相同 Dataverse 文件上传流程的专用辅助程序。仅支持 JSON 的 dataverse-request.js BATCH-RECORDS 模式不支持二进制上传。

**硬性失败规则:**如果示例行的媒体上传失败,请保留元数据行,在 Step 6 中记录媒体失败,并继续处理。不要删除该行或重新运行架构创建。应用仍应能使用占位图像回退来展示可用的列表和详情数据。

切勿执行以下操作:

  • 除非列的类型确实是 URL/Text,否则切勿将 CDN URL 写入 File/Image 列。
  • 切勿将 base64 填入 File 列。
  • 切勿将 UI 主视觉图、启动图或图标资源上传到 Dataverse。
  • 切勿猜测 columnName;应使用 .datamodel-manifest.json / field_bindings.file_columns 中的确切逻辑名称。
步骤 5e — 跟踪结果 + memory-bank

BATCH-RECORDS 响应是你的跟踪依据。对于每个层级的响应数组:

  • 成功(status 为 200-299,且存在 recordId)→ 追加到内存中的 { index → { table, primaryNameValue, recordId } } 映射。
  • 失败(status 为 0 / 4xx / 5xx,且带有 error)→ 追加到各层级的失败列表,供步骤 6 汇总。

所有层级完成后(或严格默认模式停止后),批量写入 memory-bank.md 中的 ## Seeded sample data,以确保后续运行具有幂等性。每次运行中每个表占一行:

## Seeded sample data

| Date | Table | Records inserted | First GUID | Last GUID |
|---|---|---|---|---|
| <ISO> | cr3e9_jobsite | 5 | <guid1> | <guid5> |

运行结束后,在单次 Edit 调用中写入所有行,而不是逐层级写入——这样可避免迭代期间并发写入文件造成竞争,并确保审计日志具有原子性。

步骤 5f — 从失败的层级恢复 / 继续

如果某个层级中途失败,需要重试时,不要编写手工制作的 seed-resume.js,将之前运行的标准输出中的父级 GUID 写死其中。 这种做法是导致 Entity '<table>' With Id = <guid> Does Not Exist 404 错误的头号原因,因为 (a) 之前的运行可能实际上并未提交它所打印的父级行;(b) 重新创建的表所对应的 GUID 会立即过期。

唯一安全的恢复模式:

恢复前,重新确认当前获批的 seedTables 和退役排除项。之前 memory-bank 中的成功记录或种子文件,并不能授权使用已从当前范围移除的表;绝不要重试正在退役的表。

  1. 通过稳定的业务键重新查询父级 GUID。 对失败层级的 @odata.bind 值中引用的每个父级表,使用该行的自然标识符(名称、尾号、代码——即播种时用作主名称的值)执行一次全新的 GET 筛选查询。示例:

    cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
    node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \
      "<parentEntitySet>?\$select=<parentIdColumn>,<naturalKey>&\$filter=startswith(<naturalKey>,'<seed prefix>')&\$top=50" \
      --tenant-id '<tenantId-from-resolve-environment>'
    

    根据响应构建新的 { naturalKey → guid } 映射。

  2. 重新查询每个查找目标的 EntitySetName(步骤 5a)。不要信任之前会话中缓存的值——表的 EntitySetName 是稳定的,但缓存文件可能来自其他项目。

  3. 如果父级查询返回的行数少于预期,立即停止。 不要使用不完整的映射继续操作——将数量告知用户:

    ⛔ Resume aborted: expected <N> parent rows in <table>, found <K>. Re-run /add-sample-data from Tier 0 (don't trust the prior session's GUIDs).

  4. 使用全新查询到的 GUID 和 EntitySetName 替换操作数组中的值, 按照常规步骤 5b/5c 发起 BATCH-RECORDS 调用,重新提交失败的层级。使用相同的 --solution 标志、从已验证的步骤 1 身份信息中明确指定的 --tenant-id,并遵守并发规则;绝不要为了恢复而切换租户。

  5. 继续执行后续层级, 使用本次恢复响应中的数据填充新的内存 { index → recordId } 映射。

为什么目前没有 --resume 标志: 严格默认的失败模式(“停止该层级、显示失败情况,让用户重新运行”)与上述实时查询规则结合起来,就是恢复机制。标志会让人以为脚本能自动判断哪些数据已存在——事实并非如此,因为部分提交和并发编辑会使任何缓存都不可靠。恢复决策由用户负责;Agent 的职责是遵循实时查询模式,绝不臆造有状态的恢复辅助工具。

步骤 6 — 汇总
✅ Sample data seeded
─────────────────────────────────────────────
Environment   : <envUrl>
Solution      : <solution>
Seed scope    : <approved targets; excluded/retiring targets>
Tables seeded : <list with counts, e.g. cr3e9_jobsite (5), cr3e9_inspection (5)>
Total records : <N>
Failures      : <K> (see list below if K > 0)

Next steps:
  npm run dev          # Open the app — home screen now shows real data
  /preview-screens     # Generate static HTML preview with sample data
─────────────────────────────────────────────

如果有记录失败,请打印一张失败记录子表,并附上错误消息,方便用户诊断。

对于部分结果,返回非成功 / 存疑状态;绝不要声称未播种或已排除的表已成功填充。播种不会更改计划的架构,也不会从 .datamodel-manifest.json 中移除过渡条目。

硬性规则

  • 仅覆盖种子允许列表中的表。 已弃用或未经批准的表始终排除,即使它们出现在清单中,或是扇出目标所必需的。遵守行数跳过规则和标准系统表确认要求;覆盖目标绝不能成为扩大种子范围的理由。
  • 每个父项的扇出下限——Tier K-1 中的每个父项在 Tier K 中至少有 1 个子项(当该关系为必需关系或表示一对多时)。随机分配查找值会留下孤立的父项,并破坏父项-详情页面。应逐个父项生成子行,而不是将所有行作为一个批次随机选择父项。
  • 事务、问题、覆盖和日志表的状态分布——至少混合 2 种不同的值;绝不能全部为 Open、全部为 InProgress 或全部为 Pending。各类别的目标请参见 Step 4a。
  • 事务表/日志表的日期分布——在今天及过去 14 天内分散分布,不要集中在某一天。约 30% 为近期、50% 为上周、20% 为更早日期。
  • 实际插入时使用 BATCH-RECORDS 模式——使用单个 Node 进程,在同一层内并行处理,按序返回结果,并在遇到 429 时自适应调整并发数。不要逐行调用 node dataverse-request.js POST ...:每次冷启动约需 150-300ms,而批处理模式的目的正是摊薄这部分开销。
  • 不同层之间始终顺序执行,同一层内始终并行执行。 子项需要父项 GUID;同一层中的行彼此独立。弄反了,要么会产生孤立的查找值(并行度过高),要么会浪费时间(顺序执行过多)。
  • 并发上限为 5(BATCH-RECORDS 的默认值)。脚本在首次遇到 429 时会自动降至 3。除非你已测量并确认该环境的服务保护上限,否则不要将 --concurrency 设置得更高。
  • 并行仅适用于记录插入。 架构操作(EntityDefinitions、Attributes、RelationshipDefinitions POST 请求)必须保持顺序执行——Dataverse 元数据锁。现有 /add-dataverse Step 5 并发规则不变。
  • 默认情况下,绝不为标准系统表(contact、account、incident 等)生成种子数据——这些表与环境中的其他应用共享。如果用户明确选择这些表,须显示确认提示:“这些记录将对该环境中的所有应用可见。是否继续?”
  • 始终传入 --solution,确保记录归入我们的解决方案。
  • 查找值使用 @odata.bind,其值格式为 PascalCase 导航属性名称加 /entitySet(guid)。 其他格式要么会静默丢弃关系,要么返回 400,或以 Entity '<target>' With Id = <guid> Does Not Exist 返回 404。BATCH-RECORDS 模式会原样传递请求正文——它不会验证 @odata.bind 的格式,这由调用方负责。
  • 查找目标的 EntitySetName 必须查询获取,绝不能猜测。 <logicalName>s 对于带版本号的表(cr3e9_inspectionv3s)和不规则复数形式(cr3e9_inquiries、cr3e9_statuses)都是错误的。必须从 EntityDefinitions(Step 5a)中读取 @odata.bind 值所引用的每个查找目标的 EntitySetName。
  • @odata.bind 中的 GUID 必须来自当前进程中的实时查询,绝不能使用之前会话的标准输出。 恢复运行时,根据自然键重新查询父项(Step 5e);绝不要把 GUID 粘贴到手工编写的 seed-resume.js 中。
  • 不要手工编写恢复脚本。 如果某一层失败,请遵循 Step 5e(重新查询,并使用常规 BATCH-RECORDS 调用重新提交失败的层)。包含硬编码 inspGuids = [...] 的 .tmp/seed-resume.js 必定会导致 404。
  • 选项列使用选项集中的实际数值 Value,而不是标签。 先查询选项集(Step 4b)。
  • 失败时不要自动回滚。 严格默认模式(交互式)会在某一层失败后停止运行;自动模式/覆盖优先模式会继续执行并跳过孤立项。无论哪种情况,清理决定都由用户负责——绝不自动 DELETE 行。

参考

评论

支持 Markdown