TSts-grm

复合值 embedded

当一组列总是作为一个整体出现(姓名、地址、坐标),可以用 prop.embedded 把它们打包成一个值对象:

// ① 作者:name 是复合值(两列打包)
const AUTHOR = model("Author", "id", class {
    id = prop.i64()
    name = prop.embedded({
        firstName: prop.str(50),
        lastName: prop.str(50),
    })
})

表结构上仍然是普通列,而且成员名直接作列名(不拼接父属性路径):name.firstName → FIRST_NAMEname.lastName → LAST_NAME。模型侧是一个结构,查询、取形、排序都按路径访问。

复合主键

主键同样可以嵌入多列,甚至嵌套:

const ORDER = model("Order", "id", class {
    // id 是一个复合结构:x 与嵌套的 y{a,b}——共三列做主键(列名 X / Y_A / Y_B)
    id = prop.embedded({
        x: prop.i32(),
        y: prop.embedded({
            a: prop.i16(),
            b: prop.i16(),
        }),
    })
    name = prop.str(10)
})

路径引用

复合值内部通过子路径访问,写法和属性访问一致:

// ② 复合值的路径:取名、按姓排序(片段:需在查询上下文中使用)
q.where(author.name.firstName.eq("Tao"))      // WHERE NAME_FIRST_NAME = ?
q.orderBy(author.name.lastName)               // ORDER BY NAME_LAST_NAME

// 复合主键的子路径:排序、查询都可以(下例取 Tag 的复合 id)
q.orderBy(tag.id.low, tag.id.high)            // 复合 id 的 low/high 分量

多列外键

关联指向复合主键时,外键就是一组列joinColumnsreferencedSubPath 把本侧列映射到对方复合键的分量:

const ORDER_ITEM = model("OrderItem", "id", class {
    id = prop.i64()
    order = prop.m2o(ORDER).joinColumns({
        columns: [
            { columnName: "order_x", referencedSubPath: "x" },
            { columnName: "order_y_a", referencedSubPath: "y.a" },
            { columnName: "order_y_b", referencedSubPath: "y.b" },
        ],
        cascade: "DELETE",
    })
})

referencedSubPath 用点号指到对方复合键的具体分量;m2m 中间表(joinThis/referenceTargetcolumns)同理支持复合键。

定位:复用字段用 embedded,不要用继承

单纯想把一组列打包复用(姓名、地址、坐标)时,用 embedded 而不是继承——继承会引入单表继承与判别列(见「继承与多态」),为了省重复代码而继承得不偿失。embedded 只是结构打包,列仍是普通列,不会改变表形态。

下一步

列、关联、形态都齐了,模型还有一个经常被高估复杂度的能力:公式与计算属性——把 SQL 表达式或 TS 计算挂到模型的普通属性上。