`query`: 创建自定义 Prisma Client 查询
Prisma Client 扩展从 4.16.0 及更高版本开始普遍可用。它们在 4.7.0 版本中以预览形式引入。如果您运行的版本早于 4.16.0,请确保您启用了clientExtensions
预览功能标志。
您可以使用query
Prisma Client 扩展 组件类型来挂钩查询生命周期并修改传入的查询或其结果。
您可以使用 Prisma Client 扩展的query
组件来创建独立的客户端。这提供了一种替代 中间件 的方法。您可以将一个客户端绑定到特定过滤器或用户,并将另一个客户端绑定到另一个过滤器或用户。例如,您可能会这样做以在行级安全性 (RLS) 扩展中获得 用户隔离。此外,与中间件不同,query
扩展组件为您提供端到端的类型安全性。 详细了解query
扩展与中间件。
扩展 Prisma Client 查询操作
使用$extends
客户端级方法 来创建一个 扩展客户端。扩展客户端是标准 Prisma Client 的变体,它被一个或多个扩展包装。
使用query
扩展组件修改查询。您可以修改以下自定义查询
要创建自定义查询,请使用以下结构
const prisma = new PrismaClient().$extends({
name?: 'name',
query?: {
user: { ... } // in this case, we add a query to the `user` model
},
});
属性如下
name
:(可选)指定扩展的名称,该名称将显示在错误日志中。query
: 定义自定义查询。
修改特定模型中的特定操作
query
对象可以包含映射到 Prisma Client 操作 名称的函数,例如findUnique()
、findFirst
、findMany
、count
和create
。以下示例将user.findMany
修改为使用自定义查询,该查询仅查找年龄大于 18 岁的用户
const prisma = new PrismaClient().$extends({
query: {
user: {
async findMany({ model, operation, args, query }) {
// take incoming `where` and set `age`
args.where = { ...args.where, age: { gt: 18 } }
return query(args)
},
},
},
})
await prisma.user.findMany() // returns users whose age is greater than 18
在上面的示例中,对prisma.user.findMany
的调用会触发query.user.findMany
。每个回调都接收一个类型安全的{ model, operation, args, query }
对象,该对象描述了查询。此对象具有以下属性
-
model
: 我们要扩展的查询所包含模型的名称。在上面的示例中,
model
是类型为"User"
的字符串。 -
operation
: 正在扩展和执行的操作的名称。在上面的示例中,
operation
是类型为"findMany"
的字符串。 -
args
: 要扩展的特定查询输入信息。这是一个类型安全的对象,您可以在查询发生之前对其进行修改。您可以修改
args
中的任何属性。异常:您不能修改include
或select
,因为这会更改预期的输出类型并破坏类型安全性。 -
query
: 查询结果的 Promise。- 您可以使用
await
然后修改此 Promise 的结果,因为它的值是类型安全的。TypeScript 会捕获对该对象的任何不安全修改。
- 您可以使用
修改模式中所有模型的特定操作
要扩展模式中所有模型的查询,请使用$allModels
而不是特定模型名称。例如
const prisma = new PrismaClient().$extends({
query: {
$allModels: {
async findMany({ model, operation, args, query }) {
// set `take` and fill with the rest of `args`
args = { ...args, take: 100 }
return query(args)
},
},
},
})
修改特定模型中的所有操作
使用$allOperations
来扩展特定模型中的所有操作。
例如,以下代码将自定义查询应用于user
模型上的所有操作
const prisma = new PrismaClient().$extends({
query: {
user: {
$allOperations({ model, operation, args, query }) {
/* your custom logic here */
return query(args)
},
},
},
})
修改所有 Prisma Client 操作
使用$allOperations
方法修改 Prisma Client 中存在的所有查询方法。$allOperations
可用于模型操作和原始查询。
您可以按如下方式修改所有方法
const prisma = new PrismaClient().$extends({
query: {
$allOperations({ model, operation, args, query }) {
/* your custom logic for modifying all Prisma Client operations here */
return query(args)
},
},
})
在调用 原始查询 的情况下,传递给回调的model
参数将为undefined
。
例如,您可以使用$allOperations
方法记录查询,如下所示
const prisma = new PrismaClient().$extends({
query: {
async $allOperations({ operation, model, args, query }) {
const start = performance.now()
const result = await query(args)
const end = performance.now()
const time = end - start
console.log(
util.inspect(
{ model, operation, args, time },
{ showHidden: false, depth: null, colors: true }
)
)
return result
},
},
})
修改模式中所有模型的所有操作
使用$allModels
和$allOperations
来扩展模式中所有模型的所有操作。
要将自定义查询应用于模式中所有模型上的所有操作
const prisma = new PrismaClient().$extends({
query: {
$allModels: {
$allOperations({ model, operation, args, query }) {
/* your custom logic for modifying all operations on all models here */
return query(args)
},
},
},
})
修改顶层原始查询操作
要将自定义行为应用于特定顶层原始查询操作,请使用顶层原始查询函数的名称,而不是模型名称
- 关系型数据库
- MongoDB
const prisma = new PrismaClient().$extends({
query: {
$queryRaw({ args, query, operation }) {
// handle $queryRaw operation
return query(args)
},
$executeRaw({ args, query, operation }) {
// handle $executeRaw operation
return query(args)
},
$queryRawUnsafe({ args, query, operation }) {
// handle $queryRawUnsafe operation
return query(args)
},
$executeRawUnsafe({ args, query, operation }) {
// handle $executeRawUnsafe operation
return query(args)
},
},
})
const prisma = new PrismaClient().$extends({
query: {
$runCommandRaw({ args, query, operation }) {
// handle $runCommandRaw operation
return query(args)
},
},
})
修改查询的结果
您可以使用await
然后修改query
Promise 的结果。
const prisma = new PrismaClient().$extends({
query: {
user: {
async findFirst({ model, operation, args, query }) {
const user = await query(args)
if (user.password !== undefined) {
user.password = '******'
}
return user
},
},
},
})
我们包含上面的示例来表明这是可能的。但是,出于性能原因,我们建议您使用 result
组件类型 来覆盖现有字段。result
组件类型通常在这种情况下提供更好的性能,因为它仅在访问时进行计算。query
组件类型在查询执行后进行计算。
将查询包装到批处理事务中
您可以将扩展的查询包装到 批处理事务 中。例如,您可以使用它来执行行级安全性 (RLS)。
以下示例扩展了findFirst
,使其在批处理事务中运行。
const prisma = new PrismaClient().$extends({
query: {
user: {
// Get the input `args` and a callback to `query`
async findFirst({ args, query, operation }) {
const [result] = await prisma.$transaction([query(args)]) // wrap the query in a batch transaction, and destructure the result to return an array
return result // return the first result found in the array
},
},
},
})
查询扩展与中间件
您可以使用查询扩展或 中间件 来挂钩查询生命周期并修改传入的查询或其结果。客户端扩展和中间件在以下方面有所不同
- 中间件始终全局应用于同一个客户端。客户端扩展是隔离的,除非您故意将它们组合在一起。 详细了解客户端扩展。
- 例如,在行级安全性 (RLS) 场景中,您可以将每个用户保存在完全独立的客户端中。使用中间件时,所有用户都在同一个客户端中处于活动状态。
- 在应用程序执行期间,使用扩展时,您可以从一个或多个扩展客户端或标准 Prisma Client 中选择。使用中间件时,您无法选择要使用的客户端,因为只有一个全局客户端。
- 扩展受益于端到端的类型安全性推断,但中间件却没有。
您可以在所有中间件可以使用的场景中使用 Prisma Client 扩展。
如果您使用query
扩展组件和中间件
如果您在项目中使用query
扩展组件和中间件,则以下规则和优先级适用
- 在您的应用程序代码中,您必须在主 Prisma Client 实例上声明所有中间件。您不能在扩展客户端上声明它们。
- 当包含
query
组件的中介件和扩展执行时,Prisma Client 会在执行包含query
组件的扩展之前执行中介件。Prisma Client 会按照您使用$use
或$extends
实例化的顺序执行各个中介件和扩展。