iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
0
Modern Web

React + GraphQL 全端練習筆記系列 第 27

仿Trello - Prisma client CRUD

本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。

前一篇成功串接Apollo Server 跟Pirsma Client,接著就能利用 Prisma client 的 CRUD 方法翻新 server 的 resolvers。

不過只介紹仿Trello專案要用的 resolvers 的話用到的方法其實不多,還是用官方的範例完整點的簡介 Prisma Client 的 CRUD 方法。

基本 CRUD

先介紹不包含關聯查詢的CRUD方法。

findOne()

const result = await prisma.user.findOne({
  where: {
    id: 42,
  },
})
const result = await prisma.user.findOne({
  where: {
    email: 'alice@prisma.io',
  },
})

使用 where 指定查詢的欄位,該欄位必須是ID或獨特值。

對應schema 中帶有 @ID 或 @unique 標籤的欄位:

model User {
  id           Int       @id @default(autoincrement())
  //...
  email        String    @unique
  //...
}

findMany()

const user = await prisma.user.findMany({
  where: { name: 'Alice' },
})

簡易版,尋找指定欄位與目標值相等的所有資料,可以搭配sorting跟filtering實現更複雜的查詢,這些部分晚點介紹。

where參數不是必需的,當沒有參數的時候就是回傳整個table:

const users = await prisma.user.findMany()

其他幾個比較重要的參數:

  • skip : 查詢後要剃除掉幾筆排前面的資料
  • take : 指定查詢最多回傳幾筆資料

可以搭配來建立分頁功能。

const results = await prisma.post.findMany({
  skip: 40,
  take: 10,
})

findFirst()

const user = await prisma.user.findFirst({
  where: { name: 'Alice' },
})

尋找指定欄位與目標值相等的首筆資料。

const c = await prisma.post.findFirst({
    where: { name: 'Alice' },
    take: -1,
  });

需要特別介紹下 findFirst的take,跟findMany不同,並不影響回傳資料的數量,而只看正負值。

take為正值則順著序列取第一筆資料,負值就逆著序列取資料。

create()

const user = await prisma.user.create({
  data: { email: 'alice@prisma.io' },
})

用data指定要新增的資料,並回傳該筆資料。

update()

const user = await prisma.user.update({
  where: { id: 1 },
  data: { email: 'alice@prisma.io' },
})

先以where指定查詢的單筆資料後更新為data中的資料,
只有data中包含的欄位會被更新。

這裡的where跟findOne()一樣必須指定id或unique欄位。

updateMany()

const updatedUserCount = await prisma.user.updateMany({
  where: { name: 'Alice' },
  data: { name: 'ALICE' },
})

where不用指定獨特欄位,可以一次更新多筆資料。

回傳值是更新了多少筆資料的count。

upsert()

const user = await prisma.user.upsert({
  where: { id: 1 },
  update: { email: 'alice@prisma.io' },
  create: { email: 'alice@prisma.io' },
})

where必須指定獨特值欄位,
若資料存在則用update的資料進行更新,
否則用create的資料新增。

delete()

const user = await prisma.user.delete({
  where: { id: 1 },
})

刪除查詢到的單筆資料,並回傳。

deleteMany()

const deletedUserCount = await prisma.user.deleteMany({
  where: { name: 'Alice' },
})

刪除查詢到的所有資料。
回傳值是刪除了多少筆資料的count。

count()

const result = await prisma.user.count({
  where: { name: 'Alice' }
});

計算查詢到的資料筆數。

關聯查詢

include

const result = await prisma.user.findOne({
  where: { id: 1 },
  include: { posts: true },
})

query時如果欄位是跟其他model的關聯,則用include指定是否加入查詢。

如果是關聯的關聯,就繼續加在裡面:

const result = await prisma.user.findOne({
  where: { id: 1 },
  include: {
    posts: {
      author:{
        posts: true
        };
      },
    },
  },
})

Fluent API

以上的情況是查詢user情報,其中包含posts的情報,不過如是只需要該user的posts,不需要其他的情報的話呢?

Prisma的fluent API提供只回傳指定關聯欄位資料的方法,像是底下的posts() :

const postsByUser: Post[] = await prisma.user
  .findOne({ where: { email: 'alice@prisma.io' } })
  .posts()

注意雖然查詢是從user開始的,不過最終是回傳post陣列。

這種查詢方式可以一直串下去,回傳就看最後串的是哪個欄位:

const posts: Post[] = await prisma.profile
    .findOne({ where: { id: 1 } })
    .user()
    .posts()
}

巢狀新增

如果兩個model之間有關聯,則可以利用關聯欄位,同時新增兩個model的資料:

這部分搭配Schema一起解釋比較好:

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?
}
const user = await prisma.user.create({
  data: {
    email: 'alice@prisma.io',
    profile: {
      create: { bio: 'Hello World' },
    },
  },
})

以上是在新增user資料時,同時新增一筆profile資料,並且Prisma會自動新增兩者間的關聯資料。

像是Profile與User的關聯是以userId作外鍵,那Prisma就會自動建立該筆Profile 的userId資訊,確保兩筆新增資料間的關聯。

這個操作反過來從profile入手也是可以:

const user = await prisma.profile.create({
  data: {
    bio: 'Hello World',
    user: {
      create: { email: 'alice@prisma.io' },
    },
  },
})

connect

如果新增的資料,其關聯的資料已經存在,則用connect()查詢後建立關聯:

const user = await prisma.profile.create({
  data: {
    bio: 'Hello World',
    user: {
      connect: { id: 42 },
    },
  },
})

connect會先查詢資料庫中是否有這個id值的user,查不到的話新增會失敗。

connectOrCreate

const user = await prisma.profile.create({
  data: {
    bio: 'The coolest Alice on the planet',
    user: {
      connectOrCreate: { 
        where:  { email: 'alice@prisma.io' },
        create: { email: 'alice@prisma.io'}
    },
  },
})

connectOrCreate 的話則是先查詢對象是否存在,存在則建立關聯,否則新建user資料後建立關聯。

不過撰文時 connectOrCreate 還是測試版,要在schema.prisma中加入允許測試版功能的指令才能用:

generator client {
  provider             = "prisma-client-js"
  experimentalFeatures = ["connectOrCreate"] //允許指定的測試功能
}

以上巢狀的關聯功能在update,delete時也能用,搭配起來五花八門,詳細可以看看官方文件

排序

查詢多筆資料時用orderBy指定目標欄位,以及排序方向

const users = await prisma.user.findMany({
  orderBy: {
    email: 'asc',
  },
})

搜尋全部usr並依照email遞增排序。

或是遞減排序:

users = await prisma.user.findMany({
  orderBy: {
    email: 'desc',
  },
})

也可以同時排序多個欄位,關聯的資料內也可排序:

const usersWithPosts = await prisma.user.findMany({
  orderBy: [
    {
      role: 'desc',
    },
    {
      email: 'desc',
    },
  ],
  include: {       
    posts: {
      orderBy: {
        title: "desc",
      },
      select: {
        title: true,
      },
    },
  },
});

主要會用到的功能先列到這邊,稍加組合就能做到五花八門的查詢,可以多試試。

References:


上一篇
仿Trello - Apollo Server 串接 Prisma
下一篇
仿Trello - 前端 Apollo Client 串接 GraphQL API
系列文
React + GraphQL 全端練習筆記30

尚未有邦友留言

立即登入留言