跳到主要內容

複合類型

警告

複合類型僅適用於 MongoDB。

複合類型,在 MongoDB 中稱為 嵌入式文件,可讓您將記錄嵌入到其他記錄中。

我們在 v3.12.0 版本中將複合類型設為正式發佈。它們先前在 v3.10.0 版本起以預覽版提供。

本頁說明如何

  • 尋找使用 findFirstfindMany 包含複合類型的記錄
  • 建立使用 createcreateMany 包含複合類型的新記錄
  • 更新使用 updateupdateMany 現有記錄中的複合類型
  • 刪除使用 deletedeleteMany 包含複合類型的記錄

範例 schema

我們將在後續的範例中使用此 schema

schema.prisma
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

model Product {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
price Float
colors Color[]
sizes Size[]
photos Photo[]
orders Order[]
}

model Order {
id String @id @default(auto()) @map("_id") @db.ObjectId
product Product @relation(fields: [productId], references: [id])
color Color
size Size
shippingAddress Address
billingAddress Address?
productId String @db.ObjectId
}

enum Color {
Red
Green
Blue
}

enum Size {
Small
Medium
Large
XLarge
}

type Photo {
height Int @default(200)
width Int @default(100)
url String
}

type Address {
street String
city String
zip String
}

在此 schema 中,Product 模型具有 Photo[] 複合類型,而 Order 模型具有兩個 Address 複合類型。shippingAddress 是必要欄位,但 billingAddress 是選擇性欄位。

使用複合類型時的考量

在 Prisma Client 中使用複合類型時,目前有一些限制

複合類型必要欄位的預設值

從 4.0.0 版本開始,如果您在符合以下所有條件時對複合類型執行資料庫讀取,則 Prisma Client 會將預設值插入結果中。

條件

  • 複合類型上的欄位為必要欄位,且
  • 此欄位具有預設值,且
  • 此欄位未出現在傳回的文件或多個文件中。

請注意

  • 這與模型欄位的行為相同。
  • 在讀取操作中,Prisma Client 會將預設值插入結果中,但不會將預設值插入資料庫中。

在我們的範例 schema 中,假設您在 photo 中新增一個必要欄位。此欄位 bitDepth 具有預設值

schema.prisma
...
type Photo {
...
bitDepth Int @default(8)
}

...

假設您接著執行 npx prisma db push更新您的資料庫,並使用 npx prisma generate 重新產生您的 Prisma Client。然後,您執行以下應用程式碼

console.dir(await prisma.product.findMany({}), { depth: Infinity })

bitDepth 欄位沒有內容,因為您才剛新增此欄位,因此查詢會傳回預設值 8

** 較舊版本 **

在 4.0.0 版本之前,Prisma ORM 會擲回 P2032 錯誤,如下所示

Error converting field "bitDepth" of expected non-nullable
type "int", found incompatible value of "null".

使用 findfindMany 尋找包含複合類型的記錄

可以使用 where 操作中的複合類型來篩選記錄。

以下章節說明可用於依單一類型或多個類型篩選的操作,並提供每個操作的範例。

篩選單一複合類型

使用 isequalsisNotisSet 操作來變更單一複合類型

  • is:依符合的複合類型篩選結果。需要一個或多個欄位存在(例如:依寄送地址上的街道名稱篩選訂單)
  • equals:依符合的複合類型篩選結果。需要所有欄位都存在。(例如:依完整寄送地址篩選訂單)
  • isNot:依不符合的複合類型篩選結果
  • isSet:篩選選擇性欄位以僅包含已設定的結果(設定為值,或明確設定為 null)。將此篩選器設定為 true 將排除完全未設定的 undefined 結果。

例如,使用 is 篩選街道名稱為 '555 Candy Cane Lane' 的訂單

const orders = await prisma.order.findMany({
where: {
shippingAddress: {
is: {
street: '555 Candy Cane Lane',
},
},
},
})

使用 equals 篩選寄送地址中所有欄位都符合的訂單

const orders = await prisma.order.findMany({
where: {
shippingAddress: {
equals: {
street: '555 Candy Cane Lane',
city: 'Wonderland',
zip: '52337',
},
},
},
})

您也可以使用此查詢的簡寫表示法,省略 equals

const orders = await prisma.order.findMany({
where: {
shippingAddress: {
street: '555 Candy Cane Lane',
city: 'Wonderland',
zip: '52337',
},
},
})

使用 isNot 篩選郵遞區號不是 '52337' 的訂單

const orders = await prisma.order.findMany({
where: {
shippingAddress: {
isNot: {
zip: '52337',
},
},
},
})

使用 isSet 篩選已設定選擇性 billingAddress 的訂單(設定為值或 null

const orders = await prisma.order.findMany({
where: {
billingAddress: {
isSet: true,
},
},
})

篩選多個複合類型

使用 equalsisEmptyeverysomenone 操作來篩選多個複合類型

  • equals:檢查列表是否完全相等
  • isEmpty:檢查列表是否為空
  • every:列表中的每個項目都必須符合條件
  • some:列表中一個或多個項目必須符合條件
  • none:列表中沒有任何項目可以符合條件
  • isSet:篩選選擇性欄位以僅包含已設定的結果(設定為值,或明確設定為 null)。將此篩選器設定為 true 將排除完全未設定的 undefined 結果。

例如,您可以使用 equals 尋找具有特定照片列表的產品(所有 urlheightwidth 欄位都必須符合)

const product = prisma.product.findMany({
where: {
photos: {
equals: [
{
url: '1.jpg',
height: 200,
width: 100,
},
{
url: '2.jpg',
height: 200,
width: 100,
},
],
},
},
})

您也可以使用此查詢的簡寫表示法,省略 equals 並僅指定您要篩選的欄位

const product = prisma.product.findMany({
where: {
photos: [
{
url: '1.jpg',
height: 200,
width: 100,
},
{
url: '2.jpg',
height: 200,
width: 100,
},
],
},
})

使用 isEmpty 篩選沒有照片的產品

const product = prisma.product.findMany({
where: {
photos: {
isEmpty: true,
},
},
})

使用 some 篩選一個或多個照片的 url"2.jpg" 的產品

const product = prisma.product.findFirst({
where: {
photos: {
some: {
url: '2.jpg',
},
},
},
})

使用 none 篩選沒有任何照片的 url"2.jpg" 的產品

const product = prisma.product.findFirst({
where: {
photos: {
none: {
url: '2.jpg',
},
},
},
})

使用 createcreateMany 建立包含複合類型的記錄

資訊

當您建立具有唯一約束的複合類型記錄時,請注意 MongoDB 不會在記錄內強制執行唯一值。深入瞭解

可以使用 set 操作在 createcreateMany 方法中建立複合類型。例如,您可以使用 create 中的 setOrder 內建立 Address 複合類型

const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
set: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
},
},
})

您也可以使用簡寫表示法,省略 set 並僅指定您要建立的欄位

const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
},
})

對於選擇性類型(如 billingAddress),您也可以將值設定為 null

const order = await prisma.order.create({
data: {
// Normal relation
product: { connect: { id: 'some-object-id' } },
color: 'Red',
size: 'Large',
// Composite type
shippingAddress: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
// Embedded optional type, set to null
billingAddress: {
set: null,
},
},
})

若要模擬 product 包含多個 photos 列表的情況,您可以一次 set 多個複合類型

const product = await prisma.product.create({
data: {
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
// New composite type
photos: {
set: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
},
})

您也可以使用簡寫表示法,省略 set 並僅指定您要建立的欄位

const product = await prisma.product.create({
data: {
name: 'Forest Runners',
price: 59.99,
// Scalar lists that we already support
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
// New composite type
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
})

這些操作也適用於 createMany 方法。例如,您可以建立多個 product,每個 product 都包含 photos 列表

const product = await prisma.product.createMany({
data: [
{
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
{
name: 'Alpine Blazers',
price: 85.99,
colors: ['Blue', 'Red'],
sizes: ['Large', 'XLarge'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 150, width: 200, url: '4.jpg' },
{ height: 200, width: 200, url: '5.jpg' },
],
},
],
})

updateupdateMany 中變更複合類型

資訊

當您更新具有唯一約束的複合類型記錄時,請注意 MongoDB 不會在記錄內強制執行唯一值。深入瞭解

可以在 updateupdateMany 方法中設定、更新或移除複合類型。以下章節說明可用於一次更新單一類型或多個類型的操作,並提供每個操作的範例。

變更單一複合類型

使用 setunsetupdateupsert 操作來變更單一複合類型

  • 使用 set 設定複合類型,覆寫任何現有值
  • 使用 unset 取消設定複合類型。與 set: null 不同,unset 會完全移除欄位
  • 使用 update 更新複合類型
  • 使用 upsert 在複合類型存在時 update,否則 set 複合類型

例如,使用 updateOrder 內的 Address 複合類型更新必要 shippingAddress

const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
shippingAddress: {
// Update just the zip field
update: {
zip: '41232',
},
},
},
})

對於選擇性嵌入類型(如 billingAddress),如果記錄不存在,請使用 upsert 建立新記錄,如果記錄存在,則更新記錄

const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
billingAddress: {
// Create the address if it doesn't exist,
// otherwise update it
upsert: {
set: {
street: '1084 Candycane Lane',
city: 'Silverlake',
zip: '84323',
},
update: {
zip: '84323',
},
},
},
},
})

您也可以使用 unset 操作來移除選擇性嵌入類型。以下範例使用 unsetOrder 中移除 billingAddress

const order = await prisma.order.update({
where: {
id: 'some-object-id',
},
data: {
billingAddress: {
// Unset the billing address
// Removes "billingAddress" field from order
unset: true,
},
},
})

您可以使用 篩選器updateMany 中更新符合複合類型的所有記錄。以下範例使用 is 篩選器來比對訂單列表上寄送地址中的街道名稱

const orders = await prisma.order.updateMany({
where: {
shippingAddress: {
is: {
street: '555 Candy Cane Lane',
},
},
},
data: {
shippingAddress: {
update: {
street: '111 Candy Cane Drive',
},
},
},
})

變更多個複合類型

使用 setpushupdateManydeleteMany 操作來變更複合類型列表

  • set:設定嵌入式複合類型列表,覆寫任何現有列表
  • push:將值推送到嵌入式複合類型列表的末尾
  • updateMany:一次更新多個複合類型
  • deleteMany:一次刪除多個複合類型

例如,使用 push 將新照片新增至 photos 列表

const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
// Push a photo to the end of the photos list
push: [{ height: 100, width: 200, url: '1.jpg' }],
},
},
})

使用 updateMany 更新 url1.jpg2.png 的照片

const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
updateMany: {
where: {
url: '1.jpg',
},
data: {
url: '2.png',
},
},
},
},
})

以下範例使用 deleteMany 刪除所有 height 為 100 的照片

const product = prisma.product.update({
where: {
id: '62de6d328a65d8fffdae2c18',
},
data: {
photos: {
deleteMany: {
where: {
height: 100,
},
},
},
},
})

使用 upsert 更新插入複合類型

資訊

當您建立或更新具有唯一約束的複合類型中的值時,請注意 MongoDB 不會在記錄內強制執行唯一值。深入瞭解

若要建立或更新複合類型,請使用 upsert 方法。您可以使用與上述 createupdate 方法相同的複合操作。

例如,使用 upsert 建立新產品或將照片新增至現有產品

const product = await prisma.product.upsert({
where: {
name: 'Forest Runners',
},
create: {
name: 'Forest Runners',
price: 59.99,
colors: ['Red', 'Green'],
sizes: ['Small', 'Medium', 'Large'],
photos: [
{ height: 100, width: 200, url: '1.jpg' },
{ height: 100, width: 200, url: '2.jpg' },
],
},
update: {
photos: {
push: { height: 300, width: 400, url: '3.jpg' },
},
},
})

使用 deletedeleteMany 刪除包含複合類型的記錄

若要移除嵌入複合類型的記錄,請使用 deletedeleteMany 方法。這也會移除嵌入式複合類型。

例如,使用 deleteMany 刪除所有 size"Small" 的產品。這也會刪除任何嵌入式 photos

const deleteProduct = await prisma.product.deleteMany({
where: {
sizes: {
equals: 'Small',
},
},
})

您也可以使用篩選器來刪除符合複合類型的記錄。以下範例使用 some 篩選器來刪除包含特定照片的產品

const product = await prisma.product.deleteMany({
where: {
photos: {
some: {
url: '2.jpg',
},
},
},
})

排序複合類型

您可以使用 orderBy 操作以升序或降序排序結果。

例如,以下命令會尋找所有訂單,並依寄送地址中的城市名稱以升序排序

const orders = await prisma.order.findMany({
orderBy: {
shippingAddress: {
city: 'asc',
},
},
})

複合類型唯一欄位中的重複值

當您對具有唯一約束的複合類型記錄執行以下任何操作時,請務必小心。在這種情況下,MongoDB 不會在記錄內強制執行唯一值。

  • 當您建立記錄時
  • 當您將資料新增至記錄時
  • 當您更新記錄中的資料時

如果您的 schema 具有帶有 @@unique 約束的複合類型,則 MongoDB 會阻止您在兩個或多個包含此複合類型的記錄中儲存受約束值的相同值。但是,MongoDB 不會阻止您在單一記錄中儲存相同欄位值的多個副本。

請注意,您可以使用 Prisma ORM 關聯來解決此問題

例如,在以下 schema 中,MailBox 具有複合類型 addresses,其在 email 欄位上具有 @@unique 約束。

type Address {
email String
}

model MailBox {
name String
addresses Address[]

@@unique([addresses.email])
}

以下程式碼在 address 中建立具有兩個相同值的記錄。MongoDB 在這種情況下不會擲回錯誤,並且會在 addresses 中儲存兩次 alice@prisma.io

await prisma.MailBox.createMany({
data: [
{
name: 'Alice',
addresses: {
set: [
{
address: 'alice@prisma.io', // Not unique
},
{
address: 'alice@prisma.io', // Not unique
},
],
},
},
],
})

請注意:如果您嘗試在兩個不同的記錄中儲存相同的值。在我們的範例中,如果您嘗試為使用者 Alice 和使用者 Bob 儲存電子郵件地址 alice@prisma.io,則 MongoDB 不會儲存資料並擲回錯誤。

使用 Prisma ORM 關聯來強制執行記錄中的唯一值

在上述範例中,MongoDB 未強制執行巢狀地址名稱的唯一約束。但是,您可以不同方式建立資料模型,以強制執行記錄中的唯一值。若要執行此操作,請使用 Prisma ORM 關聯將複合類型轉換為集合。設定與此集合的關聯,並在您要設為唯一的欄位上放置唯一約束。

在以下範例中,MongoDB 會強制執行記錄中的唯一值。MailboxAddress 模型之間存在關聯。此外,Address 模型中的 name 欄位具有唯一約束。

model Address {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
mailbox Mailbox? @relation(fields: [mailboxId], references: [id])
mailboxId String? @db.ObjectId

@@unique([name])
}

model Mailbox {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
addresses Address[] @relation
}
await prisma.MailBox.create({
data: {
name: 'Alice',
addresses: {
create: [
{ name: 'alice@prisma.io' }, // Not unique
{ name: 'alice@prisma.io' }, // Not unique
],
},
},
})

如果您執行上述程式碼,MongoDB 會強制執行唯一約束。它不允許您的應用程式新增兩個名稱為 alice@prisma.io 的地址。