# 复合值 embedded

当一组列总是作为一个整体出现（姓名、地址、坐标），可以用 `prop.embedded` 把它们打包成一个值对象：

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

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

**同名成员会静默共用一列**：正因为列名不带前缀，同一模型里若两个 embedded 含同名成员（如 `audit.deletedAt` 与 `sync.deletedAt`），或 embedded 成员与普通字段重名，最终只会生成**一列**、读写同一存储——而且**不会报错**（`a.v` 与 `b.v` 都落在 `V` 上）。复用通用字段时记得用限定名（`syncDeletedAt`）或错开结构来区分。

## 复合主键

主键同样可以嵌入多列，甚至嵌套：

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

## 路径引用

复合值内部通过**子路径**访问，写法和属性访问一致：

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

// 复合主键的子路径：排序、查询都可以（下例取 Tag 的复合 id —— 注意 `id()` 是函数，
// 每一层 embedded 都是函数调用，返回作用域后再取标量子路径）
q.orderBy(tag.id().low, tag.id().high)            // 复合 id 的 low/high 分量
```

## 多列外键

关联指向复合主键时，外键就是**一组列**。`joinColumns` 的 `referencedSubPath` 把本侧列映射到对方复合键的分量：

```ts
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/referenceTarget` 的 `columns`）同理支持复合键。

#### 引用键取形：embedded 也能 `.with` / `$flat`

多列外键在取形时可以先取**引用键**（`c.orderId`，embedded 形状），既能原样收下，也能继续取形/扁平：

```ts
// ④ 引用键原样：orderId 是一个 embedded 对象（{ x, y: { a, b } }）
const OrderKeyView = dto.view(OrderItem, c => [
    c.id,
    c.orderId,
])

// ⑤ 引用键取形 + embedded 内部 flat：只要需要的一部分，且摊平
const OrderKeyFlatView = dto.view(OrderItem, c => [
    c.id,
    c.orderId.with(c => [
        c.x,
        c.$flat("y").prefix("").with(c => [c.b]),   // y.b 摊到 orderId 本级（可 prefix("") 去前缀）
    ]),
])
```

普通 embedded 属性也支持 `c.$flat("name")`（见「查询 → 取形进阶 → $flat 扁平化」）。

## 定位：复用字段用 embedded，不要用继承

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

## 下一步

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