通过 API 请求管理关联
页面摘要: 在 REST 和 GraphQL API 请求中使用
connect、disconnect和set参数来管理内容类型(content-type)之间的关联。使用before、after、start或end等位置参数对关联进行排序。
在内容类型(被数据库层视为实体的对象)之间定义关联,就是将实体彼此连接起来。
内容类型之间的关联可以通过 admin panel(管理面板)进行管理,也可以通过 REST API 或 Document Service API 请求进行管理。
通过 Content API,可以在请求体中传入参数来连接(connect)、断开连接(disconnect)或设置(set)关联。这些请求体(payload)同时适用于单条记录的关联和多条关联(一对多、多对一、多对多以及多向关联)。当一个关联字段允许多个链接时,API 期望接收关联 ID 的数组,并在响应中返回数组。
| 参数名称 | 描述 | 更新类型 |
|---|---|---|
connect | 连接新的实体。 |
可以与 disconnect 组合使用。
可以与位置参数组合使用,以定义关联的顺序。 | 部分更新(Partial) |
| disconnect | 断开实体之间的连接。
可以与 connect 组合使用。 | 部分更新(Partial) |
| set | 将实体设置为一个特定的集合。使用 set 会覆盖到其它实体的所有现有连接。
不能与 connect 或 disconnect 组合使用。 | 完整更新(Full) |
本页描述的 connect、disconnect 和 set 请求体(payload)适用于 REST 和 GraphQL 请求,当你从服务端或插件代码中调用 Document Service 方法时,同样的对象结构也是受支持的。Document Service 的介绍中重复了这一点,本页下方的 Internationalization(国际化)示例还展示了带有 connect 的 strapi.documents(...).update()。
如果你在参照这些示例时,TypeScript 报告了 TS2353 错误,并声称 connect 不是你的 data 对象上的有效属性,请将其视为类型定义的缺口,而非 Strapi 在运行时拒绝该调用。在 Strapi 的类型包完全对齐之前,可以使用类型断言来收窄 data 请求体的类型,或者通过类型约束更宽松的小工具函数来构建它,从而保留文档中描述的结构。相关讨论请参阅 GitHub issue #2904。
当内容类型启用了 Internationalization (i18n) 时,你还可以传入一个区域(locale)来为特定的区域设置关联,如下面的 Document Service API 示例所示:
await strapi.documents('api::restaurant.restaurant').update({
documentId: 'a1b2c3d4e5f6g7h8i9j0klm',
locale: 'fr',
data: {
category: {
connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']
}
}
})
在 TypeScript 中使用 Document Service API 的 connect、disconnect 或 set 时,你可能会遇到 TS2353 错误,提示这些属性不存在于 LongHandEntity 类型上。
虽然运行时引擎完全支持这种语法,但当前的类型定义并不支持。你可以通过将数据请求体断言(cast)为 any 或更宽泛的对象类型来安全地绕过该验证:
await strapi.documents('api::cart.cart').update({
documentId: cart.documentId,
data: {
status: 'checked_out',
order: { connect: [order.documentId] } as any, // Temporary workaround
},
});
如果没有传入区域(locale),则默认使用默认区域。
connect
在请求体中使用 connect 会执行部分更新,连接指定的关联。
connect 接受简写(shorthand)或完整(longhand)两种语法:
| 语法类型 | 语法示例 |
|---|---|
| shorthand(简写) | connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] |
| longhand(完整) | connect: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }] |
你也可以使用完整(longhand)语法来对关联重新排序。
connect 可以与 disconnect 组合使用。
connect 官方不支持用于 media(媒体)属性。高级用户可以technically通过定位 upload file ID 来连接媒体条目,但这种变通方法不被 Strapi 推荐或支持,并且很容易出问题(例如,当 Draft & Publish 使用不匹配的 ID 时)。请谨慎使用。
Shorthand syntax example
发送以下请求会更新一个 restaurant(通过其 documnentId a1b2c3d4e5f6g7h8i9j0klm 标识)。该请求使用 categories 属性将这家餐厅与 2 个通过其 documentId 标识的类别连接起来:
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']
}
}
}
const fetch = require('node-fetch');
const response = await fetch(
'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm',
{
method: 'put',
body: {
data: {
categories: {
connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']
}
}
}
}
);
Longhand syntax example
发送以下请求会更新一个 restaurant(通过其 documnentId a1b2c3d4e5f6g7h8i9j0klm 标识)。该请求使用 categories 属性将这家餐厅与 2 个通过其 documentId 标识的类别连接起来:
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
connect: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' },
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
]
}
}
}
const fetch = require('node-fetch');
const response = await fetch(
'http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm',
{
method: 'put',
body: {
data: {
categories: {
connect: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' },
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
]
}
}
}
}
);
Relations reordering
(v4.6.0)
可以将位置参数传递给 connect 的完整(longhand)语法,以定义关联的顺序。
完整(longhand)语法接受一个对象数组,每个对象包含要连接的条目的 documentId,以及一个可选的 position 对象,用于定义在哪里连接该关联。
本页文档描述的语法适用于一对多、多对多以及多向关联。
对于一对一、多对一以及单向关联,也支持这些语法,但只有最后一个关联会被使用,因此最好使用更简短的格式(例如:{ data: { category: 'a1b2c3d4e5f6g7h8i9j0klm' } },请参阅 REST API 文档)。
要为某个关联定义 position,可以传入以下 4 个位置属性之一:
| 参数名称与语法 | 描述 | 类型 |
|---|---|---|
before: documentId | 将该关联定位在给定 documentId 之前。 | documentId(字符串) |
after: documentId | 将该关联定位在给定 documentId 之后。 | documentId(字符串) |
start: true | 将该关联定位在现有关联列表的开头。 | 布尔值(Boolean) |
end: true | 将该关联定位在现有关联列表的末尾。 | 布尔值(Boolean) |
position 参数是可选的,默认值为 position: { end: true }。
由于 connect 是一个数组,操作的顺序很重要,因为它们会被按顺序依次处理(请参阅下方的组合示例)。
同一个关联不应被连接超过一次,否则 API 会返回验证(Validation)错误。
Basic example
考虑数据库中的以下记录:
categories: [
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }
]
发送以下请求会更新一个 restaurant(通过其 documentId a1b2c3d4e5f6g7h8i9j0klm 标识),为 categories 属性连接一个 documentId 为 ma12bc34de56fg78hi90jkl 的实体关联,并将其定位在 documentId 为 z0y2x4w6v8u1t3s5r7q9onm 的实体之前:
请求:Example request to update the position of one relation
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
connect: [
{ documentId: 'ma12bc34de56fg78hi90jkl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } },
]
}
}
}
Combined example
考虑数据库中的以下记录:
categories: [
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }
]
在 PUT 请求的请求体中发送以下示例会更新多个关联:
请求:Example request to reorder several relations
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
connect: [
{ id: '6u86wkc6x3parjd4emikhmx', position: { after: 'j9k8l7m6n5o4p3q2r1s0tuv'} },
{ id: '3r1wkvyjwv0b9b36s7hzpxl', position: { before: 'z0y2x4w6v8u1t3s5r7q9onm' } },
{ id: 'rkyqa499i84197l29sbmwzl', position: { end: true } },
{ id: 'srkvrr77k96o44d9v6ef1vu' },
{ id: 'nyk7047azdgbtjqhl7btuxw', position: { start: true } },
]
}
}
}
省略 position 参数(如 documentId: 'srkvrr77k96o44d9v6ef1vu9')会默认使用 position: { end: true }。所有其它关联都是相对于另一个现有的 id(使用 after 或 before)或相对于关联列表(使用 start 或 end)来定位的。这些操作会按照 connect 数组中定义的顺序被依次处理,因此最终的数据库记录将如下所示:
categories: [
{ id: 'nyk7047azdgbtjqhl7btuxw' },
{ id: 'j9k8l7m6n5o4p3q2r1s0tuv' },
{ id: '6u86wkc6x3parjd4emikhmx6' },
{ id: '3r1wkvyjwv0b9b36s7hzpxl7' },
{ id: 'a1b2c3d4e5f6g7h8i9j0klm' },
{ id: 'rkyqa499i84197l29sbmwzl' },
{ id: 'srkvrr77k96o44d9v6ef1vu9' }
]
Edge cases: Draft & Publish or i18n disabled
当 Strapi 5 的某些内置功能对某个内容类型被禁用时,例如 Draft & Publish 和 Internationalization (i18),connect 参数的使用方式可能会有所不同:
从 i18n 关闭(off)的 Category 到 i18n 开启(on)的 Article 的关联:
在这种情况下,你可以选择要连接到哪个区域(locale):
data: {
categories: {
connect: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'en' },
// Connect to the same document id but with a different locale 👇
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm', locale: 'fr' },
]
}
}
从 Draft & Publish 关闭(off)的 Category 到 Draft & Publish 开启(on)的 Article 的关联:
data: {
categories: {
connect: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'draft' },
// Connect to the same document id but with different publication states 👇
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm', status: 'published' },
]
}
}
disconnect
在请求体中使用 disconnect 会执行部分更新,断开指定的关联。
disconnect 接受简写(shorthand)或完整(longhand)两种语法:
| 语法类型 | 语法示例 |
|---|---|
| shorthand(简写) | disconnect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] |
| longhand(完整) | disconnect: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }] |
disconnect 可以与 connect 组合使用。
Shorthand syntax example
发送以下请求会更新一个 restaurant(通过其 documentId a1b2c3d4e5f6g7h8i9j0klm 标识),断开与 2 个通过其 documentId 标识的条目之间的关联:
请求:Example request using the shorthand syntax
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
disconnect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'],
}
}
}
Longhand syntax example
发送以下请求会更新一个 restaurant(通过其 documentId a1b2c3d4e5f6g7h8i9j0klm 标识),断开与 2 个通过其 documentId 标识的条目之间的关联:
请求:Example request using the longhand syntax
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
disconnect: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' },
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
],
}
}
}
set
使用 set 会执行完整更新,用指定的关联替换所有现有关联,并保持指定的顺序。
set 接受简写(shorthand)或完整(longhand)语法:
| 语法类型 | 语法示例 |
|---|---|
| shorthand(简写) | set: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv'] |
| longhand(完整) | set: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }] |
由于 set 会替换所有现有关联,因此不应将其与其它参数组合使用。若要进行部分更新,请使用 connect 和 disconnect。
省略任何参数都等同于使用 set。
例如,以下 3 种语法都是等价的:
data: { categories: set: [{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' }, { documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }] }}data: { categories: set: ['z0y2x4w6v8u1t3s5r7q9onm2', 'j9k8l7m6n5o4p3q2r1s0tuv'] }}data: { categories: ['z0y2x4w6v8u1t3s5r7q9onm2', 'j9k8l7m6n5o4p3q2r1s0tuv'] }
Shorthand syntax example
发送以下请求会更新一个 restaurant(通过其 documentId a1b2c3d4e5f6g7h8i9j0klm 标识),替换所有先前存在的关联,并使用 categories 属性连接 2 个通过其 documentId 标识的类别:
请求:Example request using the shorthand syntax with set
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
set: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv4'],
}
}
}
Longhand syntax example
发送以下请求会更新一个 restaurant(通过其 documentId a1b2c3d4e5f6g7h8i9j0klm 标识),替换所有先前存在的关联,并使用 categories 属性连接 2 个通过其 documentId 标识的类别:
请求:Example request using the longhand syntax with set
PUT http://localhost:1337/api/restaurants/a1b2c3d4e5f6g7h8i9j0klm
{
data: {
categories: {
set: [
{ documentId: 'z0y2x4w6v8u1t3s5r7q9onm' },
{ documentId: 'j9k8l7m6n5o4p3q2r1s0tuv' }
],
}
}
}