解绑与删除(dissocation)
保存关联时有一个绕不开的问题:输入里没提到的现有关联成员怎么办。以 o2m 为例——书店现有的书是 {1, 2, 3},输入只给了 {3, 9},那离开集合的 1、2 是解绑(外键置空)、删除、还是直接报错?
dissocation 选项回答这个问题。注意拼写:dissocation(字段名少一个 i),三个取值:
// ① DissociateMode:当前 API 设计
import type { DissociateMode } from '@ts-grm/core'
// type DissociateMode =
// | "ERROR" // 存在未覆盖成员时拒绝执行
// | "SET_NULL" // 把外键置空(解绑,目标行保留)
// | "DELETE" // 物理删除目标行
⚠️ 设计预览:枚举与选项形态以当前 API 设计为准;以下行为语义为命名直觉 + ORM 惯例的推断,待正式实现后实测核对。
只对 o2m 生效(类型约束)
dissocation 选项的键在类型层面只收 o2m 关联(mutation_internal_types.ts:
键集合过滤为 __OneToManyPropContract 成员)——m2o/m2m 的成员不在这个选项的可寻址范围内。
这与直觉一致:m2m 的「离开集合」可以靠 associated 的替换/追加模式处理,而 o2m 的孤儿行才需要「解绑」策略。
o2m 示例:书店 × 书
沿用全书模型,BookStore.books 是 o2m 反向(外键在 book.store_id):
// ② 书店输入:books 仅引用(只给 id 集合)
// import { model, prop, dto } from '@ts-grm/core'
// const BookStore = model("BookStore", "id", class {
// id = prop.i64()
// name = prop.str(100)
// books = prop.o2m(Book).mappedBy("store").orderBy("name", "edition")
// })
// const Book = model("Book", "id", class {
// id = prop.i64()
// name = prop.str(50)
// store = prop.m2o(BookStore).joinColumns({ cascade: "DELETE" }).nullable()
// })
//
// const StoreInput = dto.input(BookStore, c => [
// c.id.key(),
// c.books.$ref("books", c => [c.id]),
// ])
// ③ 现状 {1,2,3} → 输入 {3,9},三种解绑策略(设计形态)
// await sqlClient.save(StoreInput, { id: 1n, books: [{ id: 3n }, { id: 9n }] }, {
// root: "UPDATE",
// dissocation: { "books": "SET_NULL" },
// // 书 1、2 的 store_id 置空 → 脱离该书店;9 则按需新增/追加
// })
// await sqlClient.save(StoreInput, { id: 1n, books: [{ id: 3n }, { id: 9n }] }, {
// root: "UPDATE",
// dissocation: { "books": "DELETE" },
// // 书 1、2 物理删除
// })
// await sqlClient.save(StoreInput, { id: 1n, books: [{ id: 3n }, { id: 9n }] }, {
// root: "UPDATE",
// dissocation: { "books": "ERROR" },
// // 发现未覆盖成员(1、2)→ 整个保存拒绝执行
// })
| 模式 | 未覆盖成员的下场 | 一句话 |
|---|---|---|
SET_NULL | 外键置空,行保留 | 解绑 |
DELETE | 行物理删除 | 清除 |
ERROR | 保存整体失败 | 防「忘了比」 |
SET_NULL 要求外键列可空(book.store_id 已声明 .nullable());DELETE 走的是目标模型声明的级联策略。
o2o 反向的参考级解绑标注
对 o2o 反向的仅引用输入($ref 命中 o2o 反向),还提供了两个标注型方法:
// ④ o2o 反向引用可标注 reparentable / onDissociate(设计形态,无上游测试用例)
// c.partner.$ref("partner", c => [c.id])
// .reparentable() // 允许改挂他人
// .onDissociate("DELETE") // 解绑时目标行怎么处理
reparentable():该反向外键允许被「改挂」到别的行;onDissociate(mode):解绑行为(DissociateMode同上)。
⚠️
$ref/$flatRef与这两个标注方法目前无上游测试用例,本节仅点出存在与形态。
与 associated 的配合
associated 决定「输入里的关联怎么写」,dissocation 决定「输入外的关联怎么清」——两者互补:
一个面向载荷内、一个面向载荷外。回归总结页会把同一输入 × 不同搭配的结果摆在一起对比。