模型
模型(Model)描述一张表的形状。它是整个框架的起点:视图(View)、查询 DSL、保存命令都要引用模型。
// ① 导入:model/prop 建模型,dto 建视图;sqlClient 是示例提供的客户端
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'
// ② 声明两个模型:BookStore 与 Book(双向关联;顺序无关,见下)
const BookStore = model("BookStore", "id", class {
id = prop.i64()
name = prop.str(100)
books = prop.o2m(Book).mappedBy("store")
})
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
store = prop.m2o(BookStore).nullable()
})
// ③ 同一个模型可被多个视图/查询引用——这里是第二个视图
const StoreView = dto.view(BookStore, c => [
c.id,
c.name,
c.books.with(c => [c.id, c.name, c.edition]),
])
// ④ 用第二个视图查一次:书店 + 其书(集合)
const stores = await sqlClient.createQuery(BookStore, (q, store) => {
return q.select(store.fetch(StoreView))
}).fetchList()
console.log(stores)
模型是"元数据",不是"对象工厂"
model(name, idKey, class) 做三件事:
- 登记一个命名实体(
"BookStore"/"Book"——数据库表的逻辑名); - 从
class的字段初始化器收集属性声明(字段名 →prop.xxx的定义); - 通过可选的第四个参数
ctx配置表级选项(表名、唯一约束、判别器等,见「继承与多态」)。
它不做这些事:
- 不连接数据库;
- 不实例化任何实体对象——查询结果由视图决定形状,而不是模型;
- 不触发类型反射——属性声明全部在初始化器里完成,无运行时 introspection。
定义顺序无关(惰性解析)
模型之间的相互引用(Book.store ↔ BookStore.books)可以"先引用后定义":
const A = model("A", "id", class {
b = prop.m2o(B)
})
const B = model("B", "id", class { ... })
原因是解析是惰性的:model(...) 只登记声明;class 真正被实例化(属性初始化)发生在框架第一次解析该模型时——那时模块已经全部加载完毕。唯一的例外是递归自引用(如树节点指向自己),需要显式懒加载:
parent = prop.m2o(() => TREE_NODE) // 自引用:用 () => 形式
模型与表:命名与 DDL
- 默认命名策略
UPPER_SNAKE_CASE:BookStore → BOOK_STORE、store → STORE_ID(外键列 = 属性名 +_ID); - 模型驱动建表:
createSchema(SQL 后端章)能从实体管理器汇总的模型生成建表 DDL——声明即 schema; - 属性与列是一列一属性的直接映射,无中间层。
下一步
模型由属性构成。下一节把全部属性类型过一遍:标量、可空、枚举、JSON。