关系
用查找(lookup)与主从(master-detail)连接对象 —— 级联规则、过滤选择器、层级结构、连接对象与汇总字段。
关系
**关系就是一个字段。**把 lookup 或 masterDetail 字段指向另一个对象,ObjectOS 会把上层的一切都接好:外键完整性、Console 里的记录选择器、父记录页面上的相关列表,以及查询中的 expand。
| 类型 | 基数 | 删除默认行为 | 用于 |
|---|---|---|---|
lookup | 多对一 | set_null | 松散引用(工单 → 负责人) |
masterDetail | 多对一、有归属 | cascade | 父子归属(订单 → 明细行) |
tree | 自引用 | — | 层级结构(分类、组织架构图) |
user | 多对一 | set_null | 人员选择器 —— 特化为 sys_user 的查找 |
summary | 汇总到父记录 | — | 聚合子记录(sum、count、avg) |
查找(多对一)
主力字段。引用另一个对象中的一条记录:
import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Contact = ObjectSchema.create({
name: 'contact',
fields: {
account: Field.lookup('account', {
label: 'Account',
required: true,
}),
},
});平台会校验被引用的记录确实存在 —— 每次写入都强制外键完整性。
**提示:**按所指向的对象给字段命名 —— 用
account,而不是account_id。这样相关列表、expand和 AI Builder 读起来都更顺。
父记录被删除时会发生什么
用 deleteBehavior 控制级联行为:
| 值 | 父记录删除时 |
|---|---|
set_null | 子记录的引用被清空(lookup 默认) |
cascade | 子记录也一并删除 |
restrict | 只要还有子记录,就阻止删除 |
过滤查找
约束选择器提供哪些记录。静态条件用 lookupFilters;用 dependsOn 让候选记录随同一记录上另一个字段的取值收窄:
contact: Field.lookup('contact', {
label: 'Contact',
dependsOn: ['account'], // 只显示所选 account 下的联系人
lookupFilters: [
{ field: 'is_active', operator: 'eq', value: true },
],
})警告:旧的
referenceFilters: string[]属性(如['is_active = true'])虽然能通过 Schema 校验,但记录选择器 UI 并不读取它 —— 它什么都不过滤。请始终使用上面展示的结构化lookupFilters+dependsOn。
主从(有归属的子记录)
masterDetail 是带归属语义的查找:子记录属于父记录,默认随父级联删除,并且可以在父表单上以明细行的形式内联编辑。
export const OrderLine = ObjectSchema.create({
name: 'order_line',
fields: {
order: Field.masterDetail({ reference: 'order', label: 'Order' }),
product: Field.lookup('product', { label: 'Product' }),
quantity: Field.number({ label: 'Quantity', min: 1 }),
},
});| 方面 | 行为 |
|---|---|
| 删除 | 运行时级联到子记录,除非 deleteBehavior: 'restrict' |
| 归属 | 子记录归父记录所有 |
| 编辑 | 可选:在父表单上内联编辑明细行 |
| 汇总 | summary 字段只在主(master)侧有效 |
引用是可选或共享的,用 lookup;子记录离开父记录就没有意义的,用 masterDetail。
自引用查找与树
查找可以指回自己的对象,用来构建层级结构:
parent_account: Field.lookup('account', {
label: 'Parent Account',
description: 'Parent company in hierarchy',
})对于专门的层级结构(分类、组织架构图),使用 tree 字段类型 —— 一个存储和展开方式与 lookup 相同的自引用查找。注意引擎在写入时不做环路检查,所以一条绕回自身的链不会被自动拒绝。
**提示:**把
tree/ 自引用查找字段与树形列表视图搭配使用,即可在 Console 中把层级渲染为嵌套行。
相关列表
这些是免费得到的。当查找指向父对象时,子记录会自动以相关列表的形式出现在父记录的详情页上:
- Contact 有
account: Field.lookup('account') - → Account 详情页会显示一个 Contacts 相关列表
无需任何额外配置。
多对多:连接对象
ObjectOS 没有直接的多对多字段 —— 用连接对象来建模:一个带两个关系字段的对象,各指向一侧。
// Student ↔ Course,通过 Enrollment 连接
export const Enrollment = ObjectSchema.create({
name: 'enrollment',
fields: {
student: Field.masterDetail({ reference: 'student', label: 'Student' }),
course: Field.masterDetail({ reference: 'course', label: 'Course' }),
grade: Field.select({ label: 'Grade', options: [ /* ... */ ] }),
},
indexes: [
{ fields: ['student', 'course'], unique: true }, // 每对组合只允许一条报名记录
],
});两侧都会把连接记录看作一个相关列表,而连接对象本身正是存放这段配对属性(成绩、角色、加入日期)的天然位置。
汇总(summary 字段)
用 summary 字段把子记录聚合到父记录上。只在主对象上有效 —— 即主从关系的父侧:
// 在 Order 对象上
total_lines: {
type: 'summary',
label: 'Line Count',
summaryOperations: {
object: 'order_line', // 要聚合的子对象
field: 'quantity', // 要聚合的字段
function: 'sum', // count | sum | min | max | avg
},
}Summary 字段是只读的,由子记录计算得出 —— 你永远不会直接写它。
跨关系查询
用 expand 在一次调用里加载关联记录 —— 查询 API 会顺着查找字段把被引用的记录内联进来,客户端不必发出 N+1 次请求。