视图
ts-grm 查询不返回实体对象,而是视图声明的精确 DTO 形状。视图就是"列一张想要的清单"。
先准备一组带关联的模型(模型页讲过:外键在 Book,BookStore.books 是它的反向集合):
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()
})
基本取形
声明一个视图,列出想要的字段:
// ① 导入:model/prop 建模型,dto 建视图;sqlClient 是示例提供的客户端
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'
// ② 声明模型:Book 表四列(本示例先不带关联,聚焦视图本身)
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
})
// ③ 声明视图:只要这三个字段 → 结果就只有这三个字段
const BookView = dto.view(Book, c => [
c.id,
c.name,
c.price,
])
// ④ 查询:全表按视图取回
const rows = await sqlClient.createQuery(Book, (q, book) => {
return q.select(book.fetch(BookView))
}).fetchList()
console.log(rows)
c.id/c.name直接选标量列,未列出的字段不会出现;- 全选标量写
c.$allScalars,剔除个别字段用.exclude("price"); - 视图是类型也是运行时装订:
rows的类型会推导为{ id, name, price }[],取错字段名编译期就报错。
嵌套取形:顺着关联取
关联两边都能按需深入——多对一用 .with 取对象,一对多用 .with 取数组:
// 模型先备好双向关联(同一张外键的两种视角):
// Book.store m2o:拥有方,生成外键列 STORE_ID,外键可空(.nullable())
// BookStore.books o2m:反向集合,mappedBy 指向 Book.store,不产生列
// m2o 方向:每本书带它的书店(store 是对象,无关联时为 null)
const BookWithStoreView = dto.view(Book, c => [
c.$allScalars,
c.store.with(c => [
c.id,
c.name,
]),
])
// o2m 方向:每个书店带它的书(books 是数组)
const StoreWithBooksView = dto.view(BookStore, c => [
c.id,
c.name,
c.books.with(c => [
c.id,
c.name,
c.edition,
]),
])
两种方向的 SQL 行为不同:store.with 一次 JOIN 或按主键批量加载;books.with 是多行展开成数组(「查询 API → 关联预取」细讲)。取形的结果里 store 是对象(或 null),books 是数组。
关联可以不对称
m2o 与 o2m 是同一张外键的两种视角,但它们不需要成对出现:
- 外键永远在"多"的一侧:本例外键列
store_id在book表,所以Book.store = prop.m2o(BookStore)才是"拥有方"——.nullable()决定外键可空(这本书可以没有书店); - 反向集合是可选的:
BookStore.books只是"从书店看它的书"的便利视角,写不写都不影响外键与查询;不写它,从Book出发照样能带出store; - 需要时再补反向:
prop.o2m(Book).mappedBy("store"),"store"必须是Book上真实存在的 m2o 属性名——mappedBy 名字写错会在模型解析时报错。
这正是建表的直觉:外键建在哪张表,关联就在哪个模型上声明;反向只是视图层便利。o2m 永远与某个 m2o(或 o2o)配对,没有独立存在——这也是它叫"反向"的原因。
为什么是"视图"而不是实体
传统 ORM 把记录实例化成实体对象,字段是模型全集;ts-grm 反过来——形状由查询方决定。带来的变化:
- 网络传输与对象构造只做必要功,少字段少成本;
- 同样的模型按场景取不同形状:列表页不要
price、详情页要全部; - 与保存侧呼应(Input DTO 按需声明"可写哪些字段"),一套模型两种方向。
下一步
视图是"想要的形状",查询 DSL 是"怎么筛选、怎么排序"。下一节跑通完整的查询。