iT邦幫忙

2024 iThome 鐵人賽

DAY 10
1
Modern Web

一些讓你看來很強的 ORM - prisma系列 第 10

Day10. 一些讓你看來很強的 ORM - prisma (relation)介紹

  • 分享至 

  • xImage
  •  

今天要來介紹 relation,在 prisma 中你可以定義兩個 model 去關聯彼此的關係,舉個例子以下是兩個 model Userpost ,那因為一個 User 可以有很多個 Post 所以 UserPost 是一個一對多的關係,在 prisma 中會透過 @relation 去表示兩個 model 的關係是一對多、ㄧ對一還是多對多等。

model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  name   String
  author   User @relation(fields: [authorId], references: [id])
  authorId Int // relation scalar field  (used in the `@relation` attribute above)
}

這邊補充一個小知識,在 Prisma 中透過 @relation 定義的 fields 如上方的 author 這個欄位,實際上並不會出現在 DB 的欄位中,他只是讓 Prisma 知道兩個 Model 之間的關聯性,讓 Prisma client 知道要如何 generate

那至於 authorId 則是會實際存在 DB 中,透過 @relationreferenced,因為它代表著是一個 foreign key 去關聯 PostUser

Relational databases

以下的圖就是一個簡單的關聯式 table ,代表著一對多得關係。
https://ithelp.ithome.com.tw/upload/images/20240924/20145677O1gjxOTpWg.png

SQL 中,會使用 foreign key (FK) 在兩個表之間建立關係。FK 會存放在關聯的一側。

  • 以這邊的例子來說 FK 會是在 Post 然後 keyauthorId
  • 至於另外一邊 PK 就是 Userid ,他需要給 authorIdPost 可以關聯到 user

那在 prisma 則是透過 @relation 定義 PKFK ,所以才會需要加一個 key ,只是就像一開始說的 author 並不是一個真正的欄位。

author     User        @relation(fields: [authorId], references: [id])

Create a record

prisma 你要 create 一筆有 relationdata 有兩種方式,一種如下:

  • create a user 然後讓 prisma auto generate id
  • create 兩筆新的 post 然後 linkposts
const userAndPosts = await prisma.user.create({
  data: {
    posts: {
      create: [
        { title: 'Prisma Day 2020' }, // Populates authorId with user's id
        { title: 'How to write a Prisma schema' }, // Populates authorId with user's id
      ],
    },
  },
})

那如果是要 link 到已經有的 post 則可以透過 connect 的寫法去對到 post.id

const result = await prisma.user.create({
  data: {
    posts: {
      connect: [{ id: 8 }, { id: 9 }, { id: 10 }],
    },
  }
})

Retrieve a data

這時當你很開心的創建好資料後,想去查看 data 於是你透過 findUniqueget data

const getAuthor = await prisma.user.findUnique({
  where: {
    id: "20",
  }
});

這時你會發現並沒有 return post 的資料,那是因為 prisma 預設不會 return 所有關於 relationdata

{id:0}

所以這邊你需要加上 include 來告訴 prisma 說你 returndata 要包含 post 的部分。

const getAuthor = await prisma.user.findUnique({
  where: {
    id: "20",
  },
  include: {
    posts: true, // All posts where authorId == 20
  },
});

這樣你才會拿到 post 的資料

{id:0,posts:[{id:0,authorId:0},{id:1,authorId:0}]}

另外如果你需要要重新綁定 post 的內容到 user ,則可以透過 nest object 的寫法,重新指定 user 需要綁定哪些 post 內容。

const updateAuthor = await prisma.user.update({
  where: {
    id: 20,
  },
  data: {
    posts: {
      connect: {
        id: 4,
      },
    },
  },
})

如果你得 post id 是錯的, prismathrow 一下的 error

The required connected records were not found. Expected 1 records to be connected, found 0.

有一種情形是如果今天你不確定你得 post 是否存在,則可以透過 connectOrCreateprisma 自動幫你判斷,如果有直接 connect id 沒有就幫你 create 一筆資料,這樣你就可以減少重複的 post 資料出現。

const result = await prisma.user.create({
  data: {
    title: 'How to make croissants',
    posts: {
      connectOrCreate: {
        where: {
          name: 'post-1',
        },
        create: {
          name: 'post-1',
        },
      },
    },
  },
  include: {
    posts: true,
  },
})

如果你希望斷掉特定postrelation 則可以透過 disconnect

const result = await prisma.user.update({
  where: {
    id: 16,
  },
  data: {
    posts: {
      disconnect: [{ id: 12 }, { id: 19 }],
    },
  },
  include: {
    posts: true,
  },
})

全清的話就直接給一個 []

const result = await prisma.user.update({
  where: {
    id: 16,
  },
  data: {
    posts: {
      set: [],
    },
  },
  include: {
    posts: true,
  },
})

如果今天 postuser 是一對一的關係,要 disconnect 直接給 true 就好

const result = await prisma.post.update({
  where: {
    id: 23,
  },
  data: {
    author: {
      disconnect: true,
    },
  },
  include: {
    author: true,
  },
})

Disambiguating relations

另外一個很特別的情況是,你需要重複關聯相同的 model 到不同的欄位,以下面的例子來看:

Post 可能會有不同的 User 種類 authorpinnedBy

// NOTE: This schema is intentionally incorrect. See below for a working solution.

model User {
  id           Int     @id @default(autoincrement())
  name         String?
  writtenPosts Post[]
  pinnedPost   Post?
}

model Post {
  id         Int     @id @default(autoincrement())
  title      String?
  author     User    @relation(fields: [authorId], references: [id])
  authorId   Int
  pinnedBy   User?   @relation(fields: [pinnedById], references: [id])
  pinnedById Int?
}

但對 prisma 來說會有一個問題,因為他不知道 writtenPosts 到底是關聯到 post.author 還是 post.pinnedBy 所以 prisma 會理解成四種情況

  • User.writtenPostsPost.author + Post.authorId
  • User.writtenPostsPost.pinnedBy + Post.pinnedById
  • User.pinnedPostPost.author + Post.authorId
  • User.pinnedPostPost.pinnedBy + Post.pinnedById

那為了釐清以上的問題,你可以指定 @relationname 是什麼,然後到你要關聯的 PK model 告訴 prisma 你要用哪個 relationname

model User {
  id           Int     @id @default(autoincrement())
  name         String?
  writtenPosts Post[]  @relation("WrittenPosts")
  pinnedPost   Post?   @relation("PinnedPost")
}

model Post {
  id         Int     @id @default(autoincrement())
  title      String?
  author     User    @relation("WrittenPosts", fields: [authorId], references: [id])
  authorId   Int
  pinnedBy   User?   @relation("PinnedPost", fields: [pinnedById], references: [id])
  pinnedById Int?    @unique
}

今天大概簡單說明 relation 的概念,明天我們繼續深入他~

大家如果有問題可以來小弟的群組討論~

✅ 前端社群 :
https://lihi3.cc/kBe0Y


上一篇
Day09. 一些讓你看來很強的 ORM - prisma (model)
下一篇
Day11. 一些讓你看來很強的 ORM - prisma (relation)1-1
系列文
一些讓你看來很強的 ORM - prisma30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言