Chapter 03

关系建模

用 @relation 表达一对多、多对多、一对一与自引用,理解隐式与显式 M2M 的差别,配置级联删除。

3.1 关系的两条腿

Prisma 里关系需要在两侧 model 都声明:一侧是关系标量字段 + 关系字段(持有外键),另一侧是反向关系字段(列表或对象,虚拟,不建列)。

关系字段(relation field)
类型为另一个 model 的字段,如 author Userposts Post[]。它是 Prisma 层面的虚拟字段,不一定对应真实列。
关系标量字段(relation scalar)
真正存外键值的标量列,如 authorId Int,被 @relation(fields: ...) 引用。
@relation
关系属性,声明外键映射:fields(本表外键列)、references(对表被引用列)、name(消歧多重关系)、onDelete/onUpdate(级联动作)。
隐式多对多(implicit M2M)
两侧都写列表字段,Prisma 自动创建并管理中间连接表,无需你显式建模,适合无额外字段的纯关联。
显式多对多(explicit M2M)
自己建一个连接 model(含两条一对多关系),可在中间表加额外字段(如 assignedAtrole)。
引用动作(referential action)
onDelete/onUpdate 的取值:CascadeRestrictSetNullSetDefaultNoAction,决定父行删除/改动时子行如何处置。

3.2 一对多(One-to-Many)

model User {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]                     // 反向:一个 User 多篇 Post
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id], onDelete: Cascade)
  authorId Int                        // 外键列
}

"多"的一侧(Post)持有外键 authorId@relation;"一"的一侧(User)只写反向列表 posts Post[]

3.3 一对一(One-to-One)

model User {
  id      Int      @id @default(autoincrement())
  profile Profile?                    // 反向,可选
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique               // @unique 使其成为 1:1
}
1:1 的关键是 @unique

外键列加 @unique,才把"一对多"收窄为"一对一"。持有外键的一侧(Profile)是关系的"拥有方"。

3.4 隐式多对多

model Post {
  id         Int    @id @default(autoincrement())
  title      String
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

两侧都是列表,Prisma 自动建中间表 _CategoryToPost。写入用 connect

await prisma.post.create({
  data: {
    title: "Prisma 101",
    categories: { connect: [{ id: 1 }, { id: 2 }] },
  },
});

3.5 显式多对多(带额外字段)

model User {
  id       Int          @id @default(autoincrement())
  members  Membership[]
}

model Team {
  id       Int          @id @default(autoincrement())
  members  Membership[]
}

model Membership {
  user     User   @relation(fields: [userId], references: [id])
  userId   Int
  team     Team   @relation(fields: [teamId], references: [id])
  teamId   Int
  role     String @default("member")     // 额外字段
  joinedAt DateTime @default(now())

  @@id([userId, teamId])
}

3.6 自引用关系

// 评论树:一条评论可有父评论与多条子评论
model Comment {
  id       Int       @id @default(autoincrement())
  text     String
  parent   Comment?  @relation("Thread", fields: [parentId], references: [id])
  parentId Int?
  children Comment[] @relation("Thread")
}
多重关系用 name 消歧

同两个 model 间存在多条关系(或自引用)时,必须在 @relation("名字") 里给关系命名,让 Prisma 知道哪根字段配对哪根。

3.7 级联删除与引用动作

动作父行删除时
Cascade连带删除所有子行
Restrict若有子行则阻止删除(报错)
SetNull把子行外键置为 NULL(外键须可空)
SetDefault把子行外键设为默认值
NoAction不做处理,交给数据库约束
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)

3.8 小结与下一步