TSts-grm

解绑与删除(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 决定「输入外的关联怎么清」——两者互补: 一个面向载荷内、一个面向载荷外。回归总结页会把同一输入 × 不同搭配的结果摆在一起对比。