语义 SDK:读、写、聚合
对象集是链式的描述,最后才取数据;聚合的结果有固定形状;Action 先只校验,再执行。

本课目标
读完这一课,你将能够
- 用对象集写出一个查询:过滤、沿关系走、取一页
- 写一次聚合,并读对结果的形状:有分组和没分组不一样
- 用先只校验、再执行的方式调用 Action
对象集:链式写,最后才取
语义 SDK 里,读数据的起点是一个客户端:semantic.ontology()。在应用里它自动带上访客的身份,在 CLI 和智能体里用 Key 所属的公司。从客户端拿到一个对象类型,就得到一个对象集。where、pivotTo、union 都只是在描述集合,什么也不取,直到你调用 fetchPage、asyncIter 或 aggregate。
- 01起点
objects(type)给出一个对象集 - 02只描述
where、pivotTo、union层层收窄,不发请求 - 03才去取
fetchPage、asyncIter、aggregate发出请求
import { semantic } from "/developer/sdk/v1/aidc.js";
const client = semantic.ontology();
const running = client.objects("workOrder").where({ status: "running", plannedQty: { $gte: 40 } });
const page = await running.fetchPage({ $orderBy: { dueAt: "asc" }, $pageSize: 20 });
// page.data[i]:{ __primaryKey, __apiName, __title, status, dueAt, … }
const orders = await running.pivotTo("order").fetchPage(); // 沿关系走:这些工单属于哪些销售订单
const wo = await client.objects("workOrder").fetchOne("WO-1001"); // 按主键取一个
where 里多个属性之间是「且」;要「或」用 $or,取反用 $not,再复杂就用 $and 嵌套,最多三层。比较用 $eq、$ne、$gt、$gte、$lt、$lte、$isNull、$in,数组属性用 $contains,文字匹配用 $startsWith、$containsAnyTerm、$containsAllTerms。
这里的「类型化」有两层意思。一是返回值的形状固定:每个对象都带 __primaryKey、__apiName、__title,再加上属性。二是规则由属性的类型决定:文字匹配(如 $startsWith)只用在字符串上,大小比较用在数字、字符串和日期上,sum、avg 只用在数字上。不合规矩的写法,服务端返回 400 并说明哪里不对。
有一个常见的坑:fetchPage 只取一页,page.data.length 不是总数。要全部读完,用 asyncIter()(for await 一页一页读);要数量,用聚合的 $count。
聚合:先选指标,再选分组
聚合回答「多少、多大」。$select 里每一项要么是 $count,要么是「属性:指标」这样一个平铺的字符串,如 "goodQty:sum";值是排序方向 unordered、asc 或 desc。$groupBy 决定怎么分组:exact 按值、$fixedWidth 等宽分桶、$ranges 自定区间、$duration 按时间。exact 分组的组数缺省上限是 10,000。
const byLine = await client.objects("workOrder")
.where({ status: "done" })
.aggregate({
$select: { $count: "unordered", "goodQty:sum": "desc", "scrapQty:sum": "unordered" },
$groupBy: { lineCode: "exact" },
});
// 有分组:[{ $group: { lineCode: "L01" }, $count: 12, goodQty: { sum: 470 }, scrapQty: { sum: 9 } }, …]
// 没有 $groupBy:一个对象 { $count: 12, goodQty: { sum: 470 }, … }
指标能用在哪,看属性的类型:sum、avg 只用于数字,min、max 还能用于日期和时间戳,approximateDistinct、exactDistinct 什么类型都行。读结果有两个坑:带分组的聚合返回数组,没有对象匹配时是空数组,先判断再取 [0];avg、min、max 在没有匹配对象时是 undefined,不要当成 0。
带 $groupBy | 不带 $groupBy | |
|---|---|---|
| 返回什么 | 数组,每组一行:$group 加各项指标 | 一个对象:$count 和各项指标 |
| 没有对象匹配 | 空数组,先判断再取 [0] | $count 是 0,avg、min、max 是 undefined |
| 什么时候用 | 要按产线、按月这样分开看 | 只要一个总数,或一个总和 |
派生属性也可以临时加。withProperties 给对象集加上一个沿关系、读的时候才算出来的值,最多 3 跳,只读。
const lines = await client.objects("productionLine")
.withProperties({ orderCount: (b) => b.pivotTo("workOrders").aggregate("$count") })
.fetchPage();
Action:先校验,再执行
改数据只有一个入口,就是 Action。SDK 里 client.action(name) 给出一个 Action,applyAction 接收参数和选项。加上 $validateOnly: true 只校验,不写任何东西,返回 validation.result(VALID 或 INVALID),参数和提交条件逐项列出。确认没问题,再去掉它,换成 $returnEdits: true,看这次改了哪些对象和链接。
const hold = client.action("hold-work-order");
const check = await hold.applyAction({ workOrder: "WO-1001", reason: "来料不良" }, { $validateOnly: true });
// check.validation.result:VALID / INVALID;参数与提交条件逐项列出
const done = await hold.applyAction({ workOrder: "WO-1001", reason: "来料不良" }, { $returnEdits: true });
// done.edits:改了哪些对象,新建或删除了哪些对象和链接
await hold.batchApplyAction([{ workOrder: "WO-1001" }, { workOrder: "WO-1002" }]); // 一个事务
普通 batchApplyAction 一次最多 20 个请求,放在一个事务里。有一个失败,全部不生效。配置了 writeback webhook 的 Action 一次只能提交一个请求。一次 Action 最多改 10,000 个对象、涉及 50 个 Object Type。
在应用里调用 Action,还要过两道门:应用清单的 semantic.actions 登记了它,访客的角色也在这个 Action 的 roles 里(缺省是 developer、member、editor)。只读访客永远不能写。忘了登记,请求会被拒绝。
别把「只校验」当成「一定能执行」。校验通过只说明此刻可以,执行前数据可能已经变了,最终结果要看执行的返回。
出错时,SDK 把 REST 的错误抛成异常,code 和 message 与信封里的一致;没有凭证不会发匿名请求,直接报未登录。所以 try 一下,按上一课的规则判断是改参数、等一等还是停下来。
要点
- 对象集是链式的描述:
where、pivotTo、union不取数据,fetchPage、asyncIter、aggregate才取。 - 返回值形状固定:
__primaryKey、__apiName、__title加属性;fetchPage只是一页。 - 聚合的
$select是$count或「属性:指标」;有分组返回数组,没分组返回一个对象。 - Action 先
$validateOnly再执行。普通批量最多 20 个请求;配置 writeback webhook 时一次只允许一个。
练一练
把三个问题写成 SDK 代码
用示例工厂练习:工单、产线、销售订单。写在纸上或编辑器里都行。
写一个对象集:状态为 paused 的工单,沿 order 关系找到它们的销售订单,取前 10 个。
按 lineCode 分组,统计已完成工单的良品数总和;再想想没有工单匹配时,返回值是什么。
为 hold-work-order 写「只校验」的调用,并列出你希望在 validation 里看到的三类信息。
小测
选一个答案,马上看解析。
Q1client.objects("workOrder").where({ status: "running" }) 这一行执行之后,发生了什么?
where 只在描述集合;要到 fetchPage、asyncIter 或 aggregate 才真正发请求。
Q2带 $groupBy: { lineCode: "exact" } 的聚合,没有任何对象匹配时返回什么?
有分组的聚合总是返回数组,没有匹配就是空数组;没有分组才返回一个对象。
Q3$validateOnly 校验通过之后,接着执行一定会成功吗?
校验只说明「此刻可以」;执行时数据可能已被别人改过,最终以执行的结果为准。