拓冰建站拓冰建站
首页 / 资讯中心 / 正文

NocoBase 多对多(M2M)关系字段详解:中间表设计、配置参数与 BelongsToMany 源码实现

NocoBase 多对多M2M关系字段详解中间表设计、配置参数与 BelongsToMany 源码实现【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase在 NocoBase 中构建数据模型时多对多Many to ManyM2M关系字段用于在两张数据表之间建立互相可关联多条记录的关系如学生与课程。本篇以 NocoBase 官方多对多文档为主体结合nocobase/database中BelongsToManyField、BelongsToManyRepository与关联更新逻辑的真实源码完整讲解中间表Through collection的设计、全部配置参数的含义与默认值、外键命名规则以及 M2M 字段在创建、更新、删除数据时的完整调用链。读完后你可以独立配置 M2M 关系字段并能解释其背后数据库层的构建与读写机制。为什么多对多需要中间表在选课系统中有学生和课程两个实体一个学生可以选修多门课程一门课程也可以有多个学生选修这就构成了多对多关系。在关系数据库中为了表示学生和课程之间的多对多关系通常会使用一个中间表比如选课表。这个表记录每个学生选择了哪些课程、每门课程被哪些学生选修。如上图所示Students表与Courses表通过中间的Students Courses表连接中间表持有两个外键Student ID指向Students.IDCourse ID指向Courses.ID两端的连线都标注为 1—N两个实体表上各自出现一个 Many to many 类型的关系字段。M2M 是 NocoBase 五类关系字段中的一种。从关系字段总览文档docs/docs/cn/data-sources/data-modeling/collection-fields/associations/index.md可以看到按业务语义选择一边只能关联另一边的单条记录用多对一M2O一条记录关联多条目标记录用一对多O2M两边都可以关联多条记录时就用多对多。参数说明在数据表设计中添加多对多字段时配置界面会要求你确认源表、中间表、目标表以及四个键。各参数含义如下。Source collection源表源表也就是当前字段所在表。在 NocoBase 的 M2M 字段配置界面中该项显示为只读源码中该表单项设置了x-disabled: true见packages/core/client/src/collection-manager/interfaces/m2m.tsx因为它就是当前正在加字段的那张表。Target collection目标表目标表指定与哪个表关联。这是唯一带required: true标记的表级参数必须显式选择。Through collection中间表中间表。当两个实体之间存在多对多关系时需要使用中间表来存储这种关系。中间表有两个外键用于保存两个实体之间的关联。界面上该项提示 Generated automatically if left blank——留空时由系统自动生成也可以指定一张已存在的表作为中间表例如库里已经有postsTags这样的透视表时多个关系字段可以共享同一张中间表。Source key源键源表侧外键约束引用的字段必须具备唯一性通常是主键id。Foreign key 1外键 1中间表的字段用于建立与源表之间的关联。Foreign key 2外键 2中间表的字段用于建立与目标表之间的关联。Target key目标键目标表侧外键约束引用的字段必须具备唯一性。ON DELETEON DELETE 是指在删除父表中的记录时对相关子表中的外键引用的操作规则它是用于定义外键约束时的一个选项。常见的 ON DELETE 选项包括CASCADE当删除父表中的记录时自动删除子表中与之关联的所有记录。SET NULL当删除父表中的记录时将子表中与之关联的外键值设为 NULL。RESTRICT默认选项当试图删除父表中的记录时如果存在与之关联的子表记录则拒绝删除父表记录。NO ACTION与 RESTRICT 类似如果存在与之关联的子表记录则拒绝删除父表记录。默认值与自动命名规则NocoBase 对 M2M 字段做了一层自动化客户端与数据库层各自有一套默认值生成逻辑留空的键和中间表都会按约定生成。从客户端字段接口定义packages/core/client/src/collection-manager/interfaces/m2m.tsx的initialize方法可以看到创建belongsToMany字段时配置项客户端默认值throught_${uid()}随机生成的中间表名foreignKeyf_${uid()}随机生成的外键 1otherKeyf_${uid()}随机生成的外键 2sourceKeyidtargetKeyid界面中 Foreign key 1 / Foreign key 2 的说明文案与之一致Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.随机生成、可修改支持字母、数字、下划线必须以字母开头。数据库层在字段绑定bind时还有第二套兜底规则见packages/core/database/src/fields/belongs-to-many-field.ts中间表名throughgetter 中若未显式指定则取源表名与目标表名转小写、按字典序排序后用下划线拼接再做驼峰化。例如posts与tags生成postsTagsstudents与courses生成studentsCourses。外键名checkAssociationKeys中foreignKey默认为singularize(源表名)_sourceKey的驼峰形式如post_id→postIdotherKey默认为singularize(目标表名)_targetKey如tag_id→tagId。键名sourceKey默认取源表主键targetKey默认取目标表主键。反向字段M2M 字段接口还定义了reverseFieldinterface: m2m、type: belongsToMany即配置时会在目标表上自动生成一个同类型的反向关系字段——上文 ER 图中Courses表上的Students (Many to many)字段就是这么来的两侧都可以直接查询对方列表。数据库层BelongsToManyField 如何建立关系字段定义落在packages/core/database/src/fields/belongs-to-many-field.ts的BelongsToManyField类其bind()方法完成了关系建立的完整流程校验键checkAssociationKeys若through与target指向同一张表抛出错误cannot use target collection ... as through collection逐一检查源键、外键 1、目标键、外键 2 四个字段是否存在并做类型匹配校验——中间表的foreignKey必须与源表sourceKey类型一致otherKey必须与targetKey类型一致否则抛出Foreign key ... type ... does not match source key ... type ...一类错误。准备中间表若数据库中已有该中间表则复用并回填sourceCollectionName/targetCollectionName若没有则自动创建一张isThrough: true的集合并把源表所在的 schema 一并带上。构建 Sequelize 关联调用collection.model.belongsToMany(Target, belongsToManyOptions)其中constraints: false——关联本身不在数据库层面创建物理外键约束删除行为由 NocoBase 的引用reference元数据管理同时透传throughScope、throughUnique、throughParanoid等BelongsToManyOptions类型定义见文件底部的BelongsToManyFieldOptions接口。登记引用与索引references()方法为该关联生成两条BelongsToField.toReference记录分别对应 toTarget 与 toSource 两个方向写入referenceMapON DELETE 策略取this.options.onDelete || CASCADE——即在 NocoBase 中该参数未配置时引用层默认按 CASCADE 处理这一点与数据库通用默认值RESTRICT不同配置时需留意。随后对中间表的两个外键各建一个索引Through.addIndex([foreignKey])与Through.addIndex([otherKey])。字段命名还有硬约束checkIdentifier会校验外键与中间表名的合法性过长的标识符会抛出IdentifierError。上述行为有专门的测试用例印证packages/core/database/src/__tests__/fields/belongs-to-many-field.test.ts中间表postsTags用text类型存postId/tagId而两端主键是bigIntsetField时因类型不匹配抛出错误through: t1与target: t1相同时抛出 cannot use target collection as through collection先在posts上定义{ type: belongsToMany, name: tags }此时tags表还不存在关联不会立即建立而是进入 pending 列表当tags表定义完成后postsTags中间表自动创建并带有postId、tagId两个外键字段。数据读写BelongsToManyRepository 的完整调用链M2M 字段在运行时对应packages/core/database/src/relation-repository/belongs-to-many-repository.ts中的BelongsToManyRepository它继承自MultipleRelationRepository核心方法如下create创建目标记录并建立关联create方法支持数组批量创建。单条创建时除了常规的值还支持通过values[中间表名]传递中间表的附加字段——例如中间表studentsCourses上除了两个外键还定义了enrollDate字段创建时可以一并写入{ through: values[通过表名], // 中间表附加字段的入口 transaction }创建完成后调用updateAssociations完成关联写入。add / set / toggle维护关联集合add与set都走setTargetsadd在现有基础上追加set全量替换先删后加。入参既支持目标键单个或数组也支持[targetKey, throughValues]数组形式——后者表示在写入关联的同时更新中间表上该条记录的附加字段随后通过updateThroughTableValuepackages/core/database/src/update-associations.ts按foreignKey otherKey定位中间表行并更新。toggle是切换语义先用has判断当前是否已关联已关联则remove未关联则add适合复选框类交互场景。destroy先清中间表再删目标记录destroy的删除顺序有明确保证测试packages/core/database/src/__tests__/relation-repository/belongs-to-many-repository.test.ts覆盖了该流程依据filterByTk/filter确定要删除的目标记录 id 集合按foreignKey 源记录键值 AND otherKey IN (ids)删除中间表中的关联行再删除目标表记录individualHooks: true逐条触发模型钩子。updateAssociations更新主记录时的关联同步当直接更新源记录、且载荷里携带了 M2M 字段时updateAssociationspackages/core/database/src/update-associations.ts会分发到updateMultipleAssociation其处理规则是值为null/undefinedset(null)清空全部关联值为数组时逐项判断纯键值string/number或已存在的模型实例 → 走set建立/替换关联对象项若包含目标键值且库中存在对应记录 → 仅关联add对象项缺少目标键值新记录→ 先经目标表校验collection.validate后创建新记录再关联对象项若携带以中间表名命名的字段会作为through值随关联一起写入中间表。实践要点与常见错误结合源码与测试配置 M2M 字段时需要注意中间表必须独立through不能与target相同否则在字段定义阶段就会报错。键的类型必须两端一致中间表外键与对应主键/唯一键类型不匹配如bigInt对text会抛出类型不匹配错误这一点在测试中用postsTags的text外键与bigInt主键的组合验证过。标识符有长度上限过长的foreignKey或through名测试用 128 个字符的字符串触发会抛IdentifierError自动生成的随机键名f_xxx、t_xxx就是为了避免这类问题。删除语义要区分NocoBase 层面constraints: false意味着删除行为由引用层与 Repository 的显式逻辑控制BelongsToManyRepository.destroy总是先删中间表再删目标记录而 ON DELETE 参数未显式配置时引用层默认按CASCADE处理如需RESTRICT/SET NULL等语义应在字段中显式指定。外部数据源的限制对主数据库M2M 字段会自动生成中间表、外键字段与索引对外部数据库新增关系字段只保存 NocoBase 的关系元数据不会自动创建真实外键、索引或中间表需要在数据库侧先行建好再回来同步配置见关系字段总览文档中的注意事项。延伸阅读关系字段总览五类关系类型的选型对照多对一M2O一对多O2M多对多M2M原始文档源码BelongsToManyField、BelongsToManyRepository、update-associations、M2M 客户端字段接口【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门