跳到主要内容

`query`:创建自定义 Prisma Client 查询

信息

Prisma Client 扩展从 4.16.0 及更高版本起正式可用 (GA)。它们在 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()findFirstfindManycountcreate。以下示例修改 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 中的任何属性。例外:您不能修改 includeselect,因为这将改变预期的输出类型并破坏类型安全。

  • query:查询结果的 Promise。

    • 您可以使用 await,然后修改此 Promise 的结果,因为它的值是类型安全的。TypeScript 会捕获对象上的任何不安全修改。

修改您的 Schema 中所有模型中的特定操作

要扩展您的 Schema 中所有模型的查询,请使用 $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
},
},
})

修改您的 Schema 中所有模型中的所有操作

使用 `$allModels` 和 `$allOperations` 来扩展您的 Schema 中所有模型中的所有操作。

将自定义查询应用于您的 Schema 中所有模型上的所有操作:

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)
},
},
},
})

修改特定的顶级原始查询操作

要将自定义行为应用于特定的顶级原始查询操作,请使用顶级原始查询函数的名称而不是模型名称:

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)
},
},
})

修改查询结果

您可以使用 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 transactionExtension = Prisma.defineExtension((prisma) => 
prisma.$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
},
},
},
})
)
const prisma = new PrismaClient().$extends(transactionExtension)

Query 扩展与中间件的对比

您可以使用 query 扩展或 中间件 来钩入查询生命周期,并修改传入的查询或其结果。客户端扩展和中间件在以下方面有所不同:

  • 中间件总是全局应用于同一个客户端。客户端扩展是隔离的,除非您刻意将它们组合。了解更多关于客户端扩展的信息
    • 例如,在行级安全 (RLS) 场景中,您可以将每个用户保存在完全独立的客户端中。使用中间件时,所有用户都在同一个客户端中活跃。
  • 在应用程序执行期间,使用扩展时,您可以从一个或多个扩展客户端或标准 Prisma Client 中进行选择。使用中间件时,您无法选择使用哪个客户端,因为只有一个全局客户端。
  • 扩展得益于端到端的类型安全和类型推断,而中间件则没有。

您可以在所有可以使用中间件的场景中使用 Prisma Client 扩展。

如果您同时使用 query 扩展组件和中间件

如果您在项目中同时使用 query 扩展组件和中间件,则适用以下规则和优先级:

  • 在您的应用程序代码中,您必须在主要的 Prisma Client 实例上声明所有中间件。您不能在扩展客户端上声明它们。
  • 在中间件和带有 query 组件的扩展同时执行的情况下,Prisma Client 会在执行带有 query 组件的扩展之前执行中间件。Prisma Client 按照您使用 `$use` 或 `$extends` 实例化它们的顺序执行各个中间件和扩展。