空和未定义
在 Prisma ORM 中,如果将 undefined 作为值传递,它将不包含在生成的查询中。此行为可能导致意外结果和数据丢失。为防止这种情况,我们强烈建议更新到 5.20.0 或更高版本,以利用下面描述的新 strictUndefinedChecks 预览功能。
有关当前行为(不带 strictUndefinedChecks 预览功能)的文档,请参阅当前行为。
严格的 undefined 检查(预览功能)
Prisma ORM 5.20.0 引入了一项名为 strictUndefinedChecks 的新预览功能。此功能改变了 Prisma Client 处理 undefined 值的方式,从而更好地防止意外数据丢失或意外的查询行为。
启用严格的 undefined 检查
要启用此功能,请将以下内容添加到您的 Prisma schema 中
generator client {
provider = "prisma-client"
output = "./generated"
previewFeatures = ["strictUndefinedChecks"]
}
使用严格的 undefined 检查
启用此功能后
- 在查询中将字段显式设置为
undefined将导致运行时错误。 - 要跳过查询中的字段,请使用新的
Prisma.skip符号而不是undefined。
使用示例
// This will throw an error
prisma.user.create({
data: {
name: 'Alice',
email: undefined // Error: Cannot explicitly use undefined here
}
})
// Use `Prisma.skip` (a symbol provided by Prisma) to omit a field
prisma.user.create({
data: {
name: 'Alice',
email: Prisma.skip // This field will be omitted from the query
}
})
此更改有助于防止意外删除或更新,例如
// Before: This would delete all users
prisma.user.deleteMany({
where: {
id: undefined
}
})
// After: This will throw an error
Invalid \`prisma.user.deleteMany()\` invocation in
/client/tests/functional/strictUndefinedChecks/test.ts:0:0
XX })
XX
XX test('throws on undefined input field', async () => {
→ XX const result = prisma.user.deleteMany({
where: {
id: undefined
~~~~~~~~~
}
})
Invalid value for argument \`where\`: explicitly \`undefined\` values are not allowed."
迁移路径
要迁移现有代码
// Before
let optionalEmail: string | undefined
prisma.user.create({
data: {
name: 'Alice',
email: optionalEmail
}
})
// After
prisma.user.create({
data: {
name: 'Alice',
email: optionalEmail ?? Prisma.skip
}
})
exactOptionalPropertyTypes
除了 strictUndefinedChecks,我们还建议启用 TypeScript 编译器选项 exactOptionalPropertyTypes。此选项强制可选属性必须完全匹配,这有助于捕获代码中 undefined 值的潜在问题。虽然 strictUndefinedChecks 会对无效的 undefined 使用引发运行时错误,但 exactOptionalPropertyTypes 将在构建过程中捕获这些问题。
在 TypeScript 文档中了解更多关于 exactOptionalPropertyTypes 的信息。
反馈
一如既往,我们欢迎您对该功能的反馈。请在此预览功能的 GitHub 讨论中分享您的想法和建议。
当前行为
Prisma Client 区分 null 和 undefined
null是一个值undefined意味着什么都不做
以下数据表示一个 User 表。这组数据将用于以下所有示例中
| id | 名称 | 电子邮件 |
|---|---|---|
| 1 | 尼古拉 | nikolas@gmail.com |
| 2 | 马丁 | martin@gmail.com |
| 3 | 空 | sabin@gmail.com |
| 4 | 泰勒 | tyler@gmail.com |
影响多个记录的查询中的 null 和 undefined
本节将介绍 undefined 和 null 值如何影响与数据库中多个记录交互或创建多个记录的查询行为。
Null
考虑以下 Prisma Client 查询,该查询搜索所有 name 值与提供的 null 值匹配的用户
const users = await prisma.user.findMany({
where: {
name: null,
},
})
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]
由于提供了 null 作为 name 列的过滤器,Prisma Client 将生成一个查询,搜索 User 表中所有 name 列为空的记录。
Undefined
现在考虑以下场景:您使用 undefined 作为 name 列上的过滤值运行相同的查询
const users = await prisma.user.findMany({
where: {
name: undefined,
},
})
[
{
"id": 1,
"name": "Nikolas",
"email": "nikolas@gmail.com"
},
{
"id": 2,
"name": "Martin",
"email": "martin@gmail.com"
},
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
},
{
"id": 4,
"name": "Tyler",
"email": "tyler@gmail.com"
}
]
在过滤器中使用 undefined 作为值,实际上是在告诉 Prisma Client 您已决定不为该列定义过滤器。
编写上述查询的等效方法是
const users = await prisma.user.findMany()
此查询将从 User 表中选择所有行。
在 Prisma Client 查询参数对象的任何键中使用 undefined 作为值,将导致 Prisma ORM 的行为就好像根本没有提供该键一样。
尽管本节的示例侧重于 findMany 函数,但相同的概念适用于任何可以影响多个记录的函数,例如 updateMany 和 deleteMany。
影响一个记录的查询中的 null 和 undefined
本节将介绍 undefined 和 null 值如何影响与数据库中单个记录交互或创建单个记录的查询行为。
null 不是 findUnique() 查询中的有效过滤值。
在影响单个记录的查询的过滤条件中使用 null 和 undefined 时的查询行为与上一节中描述的行为非常相似。
Null
考虑以下查询,其中 null 用于过滤 name 列
const user = await prisma.user.findFirst({
where: {
name: null,
},
})
[
{
"id": 3,
"name": null,
"email": "sabin@gmail.com"
}
]
由于 null 用作 name 列上的过滤器,Prisma Client 将生成一个查询,搜索 User 表中 name 值为空的第一条记录。
Undefined
如果使用 undefined 作为 name 列上的过滤值,则查询将表现为根本没有向该列传递过滤条件。
考虑以下查询
const user = await prisma.user.findFirst({
where: {
name: undefined,
},
})
[
{
"id": 1,
"name": "Nikolas",
"email": "nikolas@gmail.com"
}
]
在这种情况下,查询将返回数据库中的第一条记录。
表示上述查询的另一种方法是
const user = await prisma.user.findFirst()
尽管本节的示例侧重于 findFirst 函数,但相同的概念适用于任何影响单个记录的函数。
GraphQL 解析器中的 null 和 undefined
对于此示例,考虑一个基于以下 Prisma schema 的数据库
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
在以下更新用户的 GraphQL 突变中,authorEmail 和 name 都接受 null。从 GraphQL 的角度来看,这意味着字段是可选的
type Mutation {
// Update author's email or name, or both - or neither!
updateUser(id: Int!, authorEmail: String, authorName: String): User!
}
但是,如果您将 null 值用于 authorEmail 或 authorName 传递给 Prisma Client,则会发生以下情况
- 如果
args.authorEmail为null,则查询将失败。email不接受null。 - 如果
args.authorName为null,Prisma Client 会将name的值更改为null。这可能不是您希望更新工作的方式。
updateUser: (parent, args, ctx: Context) => {
return ctx.prisma.user.update({
where: { id: Number(args.id) },
data: {
email: args.authorEmail, // email cannot be null
name: args.authorName // name set to null - potentially unwanted behavior
},
})
},
相反,如果输入值为 null,则将 email 和 name 的值设置为 undefined。这样做与根本不更新字段相同
updateUser: (parent, args, ctx: Context) => {
return ctx.prisma.user.update({
where: { id: Number(args.id) },
data: {
email: args.authorEmail != null ? args.authorEmail : undefined, // If null, do nothing
name: args.authorName != null ? args.authorName : undefined // If null, do nothing
},
})
},
null 和 undefined 对条件语句的影响
使用条件进行过滤时存在一些注意事项,可能会产生意外结果。在使用条件进行过滤时,您可能会期望一个结果,但鉴于 Prisma Client 处理可空值的方式,您会收到另一个结果。
下表提供了不同运算符如何处理 0、1 和 n 个过滤器的高级概述。
| 运算符 | 0 个过滤器 | 1 个过滤器 | n 个过滤器 |
|---|---|---|---|
OR | 返回空列表 | 验证单个过滤器 | 验证所有过滤器 |
AND | 返回所有项目 | 验证单个过滤器 | 验证所有过滤器 |
NOT | 返回所有项目 | 验证单个过滤器 | 验证所有过滤器 |
此示例展示了 undefined 参数如何影响使用 OR 运算符的查询返回的结果。
interface FormData {
name: string
email?: string
}
const formData: FormData = {
name: 'Emelie',
}
const users = await prisma.user.findMany({
where: {
OR: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: []
该查询从 formData 对象接收过滤器,该对象包含一个可选的 email 属性。在此示例中,email 属性的值为 undefined。运行此查询时,不会返回任何数据。
这与 AND 和 NOT 运算符相反,如果您传入 undefined 值,它们都将返回所有用户。
这是因为将
undefined值传递给AND或NOT运算符与根本不传递任何内容相同,这意味着示例中的findMany查询将不带任何过滤器运行并返回所有用户。
interface FormData {
name: string
email?: string
}
const formData: FormData = {
name: 'Emelie',
}
const users = await prisma.user.findMany({
where: {
AND: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: { id: 1, email: 'ems@boop.com', name: 'Emelie' }
const users = await prisma.user.findMany({
where: {
NOT: [
{
email: {
contains: formData.email,
},
},
],
},
})
// returns: { id: 1, email: 'ems@boop.com', name: 'Emelie' }