Skip to content
On this page

Prisma 关系管理

Prisma 提供了强大的关系管理功能,支持一对一、一对多和多对多关系。本指南将详细介绍如何定义、查询和操作数据库关系。

关系类型

一对一关系 (One-to-One)

一对一关系表示一个模型的每个实例与另一个模型的一个实例相关联。

定义一对一关系

prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  profile Profile?
}

model Profile {
  id        Int     @id @default(autoincrement())
  bio       String?
  user      User    @relation(fields: [userId], references: [id])
  userId    Int     @unique  // 外键,也是唯一约束(确保一对一)
}

操作一对一关系

typescript
// 创建用户及其资料
async function createUserWithProfile() {
  const user = await prisma.user.create({
    data: {
      email: 'john@example.com',
      profile: {
        create: {
          bio: 'Software Developer',
        },
      },
    },
    include: {
      profile: true,
    },
  })
  return user
}

// 连接现有用户和资料
async function connectUserToProfile() {
  const profile = await prisma.profile.create({
    data: {
      bio: 'Data Scientist',
      user: {
        connect: { email: 'jane@example.com' },
      },
    },
    include: {
      user: true,
    },
  })
  return profile
}

// 查询带资料的用户
async function getUserWithProfile() {
  const user = await prisma.user.findUnique({
    where: { email: 'john@example.com' },
    include: {
      profile: true,
    },
  })
  return user
}

一对多关系 (One-to-Many)

一对多关系表示一个模型的单个实例与另一个模型的多个实例相关联。

定义一对多关系

prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]  // 一个用户可以有多篇文章
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  content  String?
  published Boolean @default(false)
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int    // 关系标量字段(外键)
}

操作一对多关系

typescript
// 创建用户和多篇文章
async function createUserWithPosts() {
  const user = await prisma.user.create({
    data: {
      email: 'author@example.com',
      name: 'Author Name',
      posts: {
        create: [
          {
            title: 'First Post',
            content: 'Content of first post',
          },
          {
            title: 'Second Post',
            content: 'Content of second post',
          },
        ],
      },
    },
    include: {
      posts: {
        where: {
          published: true,
        },
      },
    },
  })
  return user
}

// 添加新文章到现有用户
async function addPostToUser() {
  const post = await prisma.post.create({
    data: {
      title: 'New Post',
      content: 'Post content',
      author: {
        connect: { email: 'author@example.com' },
      },
    },
    include: {
      author: true,
    },
  })
  return post
}

// 查询用户及其文章
async function getUserWithPosts() {
  const user = await prisma.user.findUnique({
    where: { email: 'author@example.com' },
    include: {
      posts: {
        where: {
          published: true,
        },
        orderBy: {
          createdAt: 'desc',
        },
        take: 10,
      },
    },
  })
  return user
}

// 查询文章及其作者
async function getPostWithAuthor() {
  const post = await prisma.post.findUnique({
    where: { id: 1 },
    include: {
      author: {
        select: {
          id: true,
          email: true,
          name: true,
        },
      },
    },
  })
  return post
}

多对多关系 (Many-to-Many)

多对多关系表示两个模型的实例可以相互关联多个实例。

定义多对多关系

prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  roles     Role[]   // 多对多关系
  createdAt DateTime @default(now())
}

model Role {
  id          Int     @id @default(autoincrement())
  name        String  @unique
  description String?
  users       User[]
  permissions Permission[]
}

model Permission {
  id    Int   @id @default(autoincrement())
  name  String @unique
  roles Role[]
}

操作多对多关系

typescript
// 创建用户并分配角色
async function createUserWithRoles() {
  const user = await prisma.user.create({
    data: {
      email: 'admin@example.com',
      roles: {
        connect: [
          { name: 'admin' },
          { name: 'moderator' },
        ],
      },
    },
    include: {
      roles: true,
    },
  })
  return user
}

// 为现有用户添加角色
async function addRoleToUser() {
  const user = await prisma.user.update({
    where: { email: 'user@example.com' },
    data: {
      roles: {
        connect: { name: 'editor' },
      },
    },
    include: {
      roles: true,
    },
  })
  return user
}

// 从用户移除角色
async function removeRoleFromUser() {
  const user = await prisma.user.update({
    where: { email: 'user@example.com' },
    data: {
      roles: {
        disconnect: { name: 'editor' },
      },
      // 或者通过 ID 断开连接
      // roles: {
      //   disconnect: { id: 1 },
      // },
    },
    include: {
      roles: true,
    },
  })
  return user
}

关系字段操作

创建时连接关系

typescript
// 在创建时连接到现有记录
async function createPostWithExistingAuthor() {
  const post = await prisma.post.create({
    data: {
      title: 'Post with Existing Author',
      content: 'Content here',
      author: {
        connect: { email: 'existing@example.com' }, // 连接到现有用户
      },
    },
    include: {
      author: true,
    },
  })
  return post
}

// 在创建时创建新记录并建立关系
async function createPostWithNewAuthor() {
  const post = await prisma.post.create({
    data: {
      title: 'Post with New Author',
      content: 'Content here',
      author: {
        create: {
          email: 'newauthor@example.com',
          name: 'New Author',
        },
      },
    },
    include: {
      author: true,
    },
  })
  return post
}

更新关系

typescript
// 重新分配文章作者
async function reassignPostAuthor() {
  const post = await prisma.post.update({
    where: { id: 1 },
    data: {
      author: {
        disconnect: true, // 断开当前作者
        connect: { email: 'newauthor@example.com' }, // 连接到新作者
      },
    },
    include: {
      author: true,
    },
  })
  return post
}

// 添加多个关联记录
async function addMultipleTagsToPost() {
  const post = await prisma.post.update({
    where: { id: 1 },
    data: {
      tags: {
        connect: [
          { name: 'technology' },
          { name: 'programming' },
        ],
      },
    },
    include: {
      tags: true,
    },
  })
  return post
}

关系查询

嵌套查询

typescript
// 查询特定作者的已发布文章
async function getPublishedPostsByAuthor() {
  const posts = await prisma.post.findMany({
    where: {
      published: true,
      author: {
        email: 'author@example.com',
      },
    },
    include: {
      author: {
        select: {
          name: true,
          email: true,
        },
      },
    },
  })
  return posts
}

// 查询有特定角色的用户
async function getUsersWithSpecificRole() {
  const users = await prisma.user.findMany({
    where: {
      roles: {
        some: {
          name: 'admin',
        },
      },
    },
    include: {
      roles: true,
    },
  })
  return users
}

关系过滤器

prisma
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  comments  Comment[]
}

model Comment {
  id     Int   @id @default(autoincrement())
  content String
  post   Post  @relation(fields: [postId], references: [id])
  postId Int
  author User  @relation(fields: [authorId], references: [id])
  authorId Int
}
typescript
// 使用关系过滤器
async function getPostsWithComments() {
  const posts = await prisma.post.findMany({
    where: {
      published: true,
      comments: {
        some: {  // 至少有一个评论
          content: {
            contains: 'great',
          },
        },
      },
    },
    include: {
      comments: {
        include: {
          author: {
            select: {
              name: true,
            },
          },
        },
      },
      author: {
        select: {
          name: true,
        },
      },
    },
  })
  return posts
}

// 查询没有评论的文章
async function getPostsWithoutComments() {
  const posts = await prisma.post.findMany({
    where: {
      comments: {
        none: {}, // 没有任何评论
      },
    },
  })
  return posts
}

// 查询所有评论都被审核过的文章
async function getPostsWithAllReviewedComments() {
  const posts = await prisma.post.findMany({
    where: {
      comments: {
        every: {  // 所有评论都满足条件
          reviewed: true,
        },
      },
    },
    include: {
      comments: true,
    },
  })
  return posts
}

高级关系模式

使用关系表(Join Table)

对于需要额外字段的多对多关系,可以显式创建关系表:

prisma
model User {
  id        Int           @id @default(autoincrement())
  email     String        @unique
  memberships UserProject[]
}

model Project {
  id          Int           @id @default(autoincrement())
  name        String
  memberships UserProject[]
}

model UserProject {
  id         Int      @id @default(autoincrement())
  role       String   // 在项目中的角色
  assignedAt DateTime @default(now())
  user       User     @relation(fields: [userId], references: [id])
  userId     Int
  project    Project  @relation(fields: [projectId], references: [id])
  projectId  Int
  
  @@unique([userId, projectId]) // 防止重复关系
}
typescript
// 使用关系表
async function assignUserToProject() {
  const assignment = await prisma.userProject.create({
    data: {
      role: 'developer',
      user: { connect: { email: 'user@example.com' } },
      project: { connect: { name: 'Project A' } },
    },
    include: {
      user: true,
      project: true,
    },
  })
  return assignment
}

自引用关系

prisma
model User {
  id        Int     @id @default(autoincrement())
  email     String  @unique
  name      String?
  // 自引用关系:用户可以有经理,也可以管理其他用户
  manager   User?   @relation("EmployeeManager", fields: [managerId], references: [id])
  managerId Int?
  subordinates User[] @relation("EmployeeManager")
}
typescript
// 操作自引用关系
async function setManager() {
  const user = await prisma.user.update({
    where: { email: 'employee@example.com' },
    data: {
      manager: {
        connect: { email: 'manager@example.com' },
      },
    },
    include: {
      manager: true,
      subordinates: true,
    },
  })
  return user
}

性能优化

关系查询优化

typescript
// 避免 N+1 查询问题
async function getPostsWithAuthorsEfficiently() {
  // 好的方法:使用 include 预加载关联数据
  const posts = await prisma.post.findMany({
    include: {
      author: {
        select: {
          name: true,
          email: true,
        },
      },
    },
  })
  return posts

  // 避免的方法:在循环中单独查询每个作者
  // const posts = await prisma.post.findMany()
  // for (const post of posts) {
  //   post.author = await prisma.user.findUnique({
  //     where: { id: post.authorId }
  //   })  // 这会产生 N 次查询
  // }
}

使用 Select 优化

typescript
// 只选择需要的关联字段
async function getEfficientUserWithPosts() {
  const user = await prisma.user.findUnique({
    where: { email: 'user@example.com' },
    include: {
      posts: {
        select: {
          id: true,
          title: true,
          published: true,
          _count: {
            select: { comments: true } // 统计评论数而不获取评论详情
          }
        },
        where: {
          published: true,
        },
        orderBy: {
          createdAt: 'desc',
        },
        take: 10,
      },
    },
  })
  return user
}

关系约束和验证

强制关系存在

prisma
model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int    // 不使用 ? 表示必需
}

级联操作

prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  posts Post[]  @relation onDelete(NoAction) // 删除用户时不允许删除相关帖子
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId Int
  comments Comment[] @relation(onDelete: Cascade) // 删除帖子时级联删除评论
}

通过正确理解和使用 Prisma 的关系管理功能,您可以构建复杂的数据模型并高效地操作相关数据。