通过 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) |

关于 REST、GraphQL 与 Document Service 的关联

本页描述的 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。

NOTE

当内容类型启用了 Internationalization (i18n) 时,你还可以传入一个区域(locale)来为特定的区域设置关联,如下面的 Document Service API 示例所示:

await strapi.documents('api::restaurant.restaurant').update({ 
  documentId: 'a1b2c3d4e5f6g7h8i9j0klm',
  locale: 'fr',
  data: { 
    category: {
      connect: ['z0y2x4w6v8u1t3s5r7q9onm', 'j9k8l7m6n5o4p3q2r1s0tuv']
    }
  }
})

TypeScript Workaround

在 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 组合使用。

WARNING

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 }。

顺序(Sequential order)

由于 connect 是一个数组,操作的顺序很重要,因为它们会被按顺序依次处理(请参阅下方的组合示例)。

WARNING

同一个关联不应被连接超过一次,否则 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

省略任何参数都等同于使用 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' }
      ],
    }
  }
}