资讯动态

NocoBase 关联数据操作指南:RelationRepository 原理与实战

发布时间:2026/9/14 13:25:51 来源:尧图企业网站定制
NocoBase 关联数据操作指南RelationRepository 原理与实战【免费下载链接】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/nocobaseRelationRepository是 NocoBase 数据库层用于操作关联数据的关系型 Repository 抽象它允许开发者在不预先加载include关联对象的情况下直接对BelongsTo、HasOne、HasMany、BelongsToMany四种关联进行增删改查。本文以官方 API 文档为主线结合 relation-repository 源码 展开讲解构造函数、基类属性、四种派生 Repository 的全部类方法、中间表与事务机制帮助你在业务代码中正确、高效地操作关联数据。什么是 RelationRepository在 NocoBase 中普通Repository负责对某个 Collection 的数据进行 CRUD而RelationRepository是关系类型的Repository对象其核心价值在于在不加载关联的情况下即可对关联数据进行操作。你只需持有源记录source的主键值sourceKeyValue与关联名称association就能通过它读写目标target一侧的关联数据。基于RelationRepository每种关联都派生出了对应的实现HasOneRepository—— 一对一源侧持有外键HasManyRepository—— 一对多BelongsToRepository—— 多对一目标侧持有外键BelongsToManyRepository—— 多对多通过中间表从源码结构看它们分别继承自SingleRelationRepository与MultipleRelationRepository两个抽象基类再统一继承RelationRepository见 single-relation-repository.ts 与 multiple-relation-repository.ts。构造函数与核心参数签名constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)参数参数名类型默认值描述sourceCollectionCollection-关联中的参照关系referencing relation对应的 Collectionassociationstring-关联名称sourceKeyValuestring \| number-参照关系中对应的 key 值在 relation-repository.ts 的构造实现中构造函数会依次完成以下初始化从sourceCollection.context.database取出数据库实例并赋值给db调用setSourceKeyValue(sourceKeyValue)处理源 key 值通过this.sourceCollection.model.associations[association]取得 sequelize 的association对象通过this.sourceCollection.getField(association)取得对应的关联字段associationField由association.target得到目标模型再经database.modelCollection.get(targetModel)解析出targetCollection。其中setSourceKeyValue值得注意当传入的sourceKeyValue是字符串时会先尝试用decodeMultiTargetKey对其做decodeURIComponentJSON.parse解码见 relation-repository.ts。这意味着当源表使用复合主键时可以把多个 key 编码成一个 JSON 字符串传入配合isMultiTargetKey()判断是否为复合主键场景——这是RelationRepository支持复合主键源记录的基础。两种获取方式// 方式一通过源 Repository 的 relation() 获取推荐 const userProfileRepository User.repository.relation(profile).of(user.get(id)); // 方式二直接初始化 new HasOneRepository(User, profile, user.get(id));基类属性RelationRepository暴露了以下基类属性可在派生类与业务代码中直接访问属性类型说明dbDatabase数据库对象sourceCollectionCollection关联中的参照关系referencing relation对应的 CollectiontargetCollectionCollection关联中被参照关系referenced relation对应的 CollectionassociationAssociationsequelize 中与当前关联对应的 association 对象associationFieldRelationFieldCollection 中与当前关联对应的字段sourceKeyValueTargetKey参照关系中对应的 key 值此外源码中还提供了几个便捷成员与方法targetModel关联目标模型、sourceInstance缓存的源记录实例、collectiongetter等价于db.getCollection(targetModel.name)。getSourceModel()relation-repository.ts会按sourceKeyValue从源 Collection 查出源记录并缓存供create、remove、set等操作复用若源记录不存在多数查询型操作会返回null。基类通用方法除派生类各自实现的方法外基类RelationRepository还提供了一批可被所有关系 Repository 复用的方法create(options?: CreateOptions)创建关联对象。支持values为数组时批量创建Promise.all并发创建前会通过UpdateGuard.fromOptions做字段白名单/黑名单校验并通过collection.validate校验数据创建完成后触发{collection}.afterCreateWithAssociations与{collection}.afterSaveWithAssociations事件见 relation-repository.ts。firstOrCreate(options)按filterKeys从values中提取过滤条件先findOne命中则直接返回否则create。updateOrCreate(options)同上但命中时改为update以filterByTk定位目标记录。chunk(options)按chunkSize分块遍历关联数据每块调用callback(rows, options)适合大批量关联数据的批处理场景。convertTk/convertTks归一化tk参数支持将逗号分隔的字符串转换为数组。单值关联HasOneRepository 与 BelongsToRepositoryHasOneRepository为HasOne类型的关联 Repository一对一外键在目标表BelongsToRepository处理BelongsTo关系外键在源表两者的接口与行为完全一致belongs-to-repository.md 明确指出其接口与HasOneRepository一致。它们在源码中共同继承自SingleRelationRepository。示例初始化const User db.collection({ name: users, fields: [ { type: hasOne, name: profile }, { type: string, name: name }, ], }); const Profile db.collection({ name: profiles, fields: [{ type: string, name: avatar }], }); const user await User.repository.create({ values: { name: u1 }, }); // 获取到关联 Repository const UserProfileRepository User.repository.relation(profile).of(user.get(id)); // 也可直接初始化 new HasOneRepository(User, profile, user.get(id));find()查找关联对象不存在时返回null。签名async find(options?: SingleRelationFindOption): PromiseModelany | nullinterface SingleRelationFindOption extends Transactionable { fields?: Fields; except?: Except; appends?: Appends; filter?: Filter; }查询参数与Repository.find()一致。从源码看single-relation-repository.ts其实现会先取得源记录再由filterOptions(sourceModel)构造外键过滤条件与用户传入的filter以$and合并后交给目标 Collection 的repository.findOne执行因此天然限定在当前关联范围内。const profile await UserProfileRepository.find(); // 关联对象不存在时返回 nullcreate()创建关联对象自动写入外键。签名async create(options?: CreateOptions): PromiseModelCreateOptions类型定义见 create-options.mdinterface CreateOptions extends SequelizeCreateOptions { values?: Values; whitelist?: WhiteList; // 白名单仅名单内字段可写入 blacklist?: BlackList; // 黑名单名单内字段不允许写入 updateAssociationValues?: AssociationKeysToBeUpdate; context?: any; }const profile await UserProfileRepository.create({ values: { avatar: avatar1 }, }); console.log(profile.toJSON()); /* { id: 1, avatar: avatar1, userId: 1, updatedAt: 2022-09-24T13:59:40.025Z, createdAt: 2022-09-24T13:59:40.025Z } */注意输出中的userId: 1——关联创建时会自动补齐源记录的外键。update()更新关联对象若关联对象不存在会抛出The record does not exist。签名async update(options: UpdateOptions): PromiseModelUpdateOptions类型定义见 update-options.md其中filterByTk与filter至少要传其一interface UpdateOptions extends OmitSequelizeUpdateOptions, where { values: Values; filter?: Filter; filterByTk?: TargetKey; whitelist?: WhiteList; blacklist?: BlackList; updateAssociationValues?: AssociationKeysToBeUpdate; context?: any; }const profile await UserProfileRepository.update({ values: { avatar: avatar2 }, }); profile.get(avatar); // avatar2remove()仅解除关联关系不删除关联对象。实现上调用 sequelize 单值关联的set(null)accessor见 single-relation-repository.ts将外键置空。签名async remove(options?: Transactionable): Promisevoidawait UserProfileRepository.remove(); (await UserProfileRepository.find()) null; // true (await Profile.repository.count()) 1; // truedestroy()删除关联对象本身连带解除关联。签名async destroy(options?: Transactionable): PromiseBooleanawait UserProfileRepository.destroy(); (await UserProfileRepository.find()) null; // true (await Profile.repository.count()) 0; // trueremove与destroy对比前者外键置空、目标记录保留后者目标记录被物理删除。set()将关联设置为指定的目标记录。签名async set(options: TargetKey | SetOption): Promisevoidinterface SetOption extends Transactionable { tk?: TargetKey; }const newProfile await Profile.repository.create({ values: { avatar: avatar2 }, }); await UserProfileRepository.set(newProfile.get(id)); (await UserProfileRepository.find()).get(id) newProfile.get(id); // true多值关联HasManyRepositoryHasManyRepository用于处理HasMany一对多关系其查询类方法返回记录数组关系维护类方法add/remove/set接收单个或多个 targetKey。查询类方法find()—— 查找关联对象列表async find(options?: FindOptions): PromiseM[]查询参数与Repository.find()一致。findOne()—— 仅返回一条记录async findOne(options?: FindOneOptions): PromiseMcount()—— 返回符合查询条件的记录数async count(options?: CountOptions)interface CountOptions extends OmitSequelizeCountOptions, distinct | where | include, Transactionable { filter?: Filter; }findAndCount()—— 同时返回数据集与总数async findAndCount(options?: FindAndCountOptions): Promise[any[], number]type FindAndCountOptions CommonFindOptions;从 multiple-relation-repository.ts 的实现看findAndCount内部是分别调用find与count共享同一事务返回值形如[rows, total]非常契合分页场景。写入类方法create()与update()创建/更新关联对象options类型与上文CreateOptions/UpdateOptions一致。destroy()—— 删除符合条件的关联对象async destroy(options?: TK | DestroyOptions): PromiseMadd()—— 添加对象关联关系不创建目标记录仅建立关联async add(options: TargetKey | TargetKey[] | AssociatedOptions)interface AssociatedOptions extends Transactionable { tk?: TargetKey | TargetKey[]; }tk是关联对象的 targetKey 值可以是单个值也可以是数组。remove()—— 移除与给定对象之间的关联关系参数同add()。set()—— 设置当前关系的关联对象整体替换先移除旧的再添加新的参数同add()。多对多BelongsToManyRepositoryBelongsToManyRepository用于处理BelongsToMany多对多关系。不同于其他关系类型多对多关系需要通过中间表through来记录在 NocoBase 中定义关联关系时既可以自动创建中间表也可以明确指定中间表。中间表还可携带额外字段通过add/set一并写入。查询类方法find()、findOne()、count()、findAndCount()与HasManyRepository完全一致签名与类型定义相同此处不再重复。值得一提的是find的底层实现multiple-relation-repository.ts它会基于association.otherKey、targetKey动态构造一个指向中间表的HasOnepivot 关联as: _pivot_并通过include的方式按源记录过滤从而在目标 Repository 上完成带中间表感知的关联查询若中间表定义了scope也会被归一化后并入查询。写入类方法create()/update()/destroy()签名与HasManyRepository对应方法一致destroy返回PromiseBoolean。add()—— 添加新的关联对象支持同时写入中间表字段async add( options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions ): Promisevoidtype PrimaryKeyWithThroughValues [TargetKey, Values]; interface AssociatedOptions extends Transactionable { tk?: | TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[]; }可以直接传入关联对象的targetKey也可以将targetKey与中间表的字段值以元组[TargetKey, Values]的形式一并传入。示例const t1 await Tag.repository.create({ values: { name: t1 }, }); const t2 await Tag.repository.create({ values: { name: t2 }, }); const p1 await Post.repository.create({ values: { title: p1 }, }); const PostTagRepository new BelongsToManyRepository(Post, tags, p1.id); // 传入 targetKey await PostTagRepository.add([t1.id, t2.id]); // 传入中间表字段 await PostTagRepository.add([ [t1.id, { tagged_at: 123 }], [t2.id, { tagged_at: 456 }], ]);set()—— 设置关联对象整体替换参数同add()同样支持[TargetKey, Values]元组。remove()—— 移除与给定对象之间的关联关系仅删除中间表记录不删除目标记录async remove(options: TargetKey | TargetKey[] | AssociatedOptions)interface AssociatedOptions extends Transactionable { tk?: TargetKey | TargetKey[]; }toggle()—— 切换关联对象自动判断关联关系是否已存在存在则移除不存在则添加。适用于「收藏/取消收藏」「关注/取关」这类业务场景。async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promisevoid// 首次调用添加关联 await PostTagRepository.toggle(t1.id); // 再次调用移除关联 await PostTagRepository.toggle(t1.id);事务机制RelationRepository的多数写入方法create、update、remove、set、destroy、firstOrCreate、updateOrCreate都带有transaction()装饰器。从 relation-repository.ts 可以看到其事务工厂实现export const transaction transactionWrapperBuilder(function () { return this.sourceCollection.model.sequelize.transaction(); });行为规则为如果没有传入事务参数方法会自动创建一个内部事务若调用方通过options.transaction传入了外部事务则复用该事务见getTransactionrelation-repository.ts。这保证了关联操作与业务主流程的一致性例如add批量建立关联、toggle的判断与写入都在同一事务内原子完成。源码级原理小结关注点实现位置要点关系基类与通用能力relation-repository.ts构造函数初始化、复合主键解码、firstOrCreate/updateOrCreate/chunk、事务装饰器单值关联HasOne/BelongsTosingle-relation-repository.tsfind用$and合并外键过滤remove等价于set(null)update目标不存在时抛错多值关联HasManymultiple-relation-repository.tsfindAndCountfindcount共享事务count排除BelongsToArray关联多对多BelongsToMany同上的 pivot 关联构造find动态构造_pivot_HasOne 关联感知中间表 scope 与额外字段对应的派生实现文件分别为 hasone-repository.ts、hasmany-repository.ts、belongs-to-repository.ts、belongs-to-many-repository.ts以及统一的关系参数类型定义 types.ts。使用建议优先通过repository.relation(name).of(key)获取关系 Repository它替你完成了构造与参数归一化代码更可读直接new适合在插件内部需要显式控制场景时使用。区分remove与destroy解除关系用remove数据保留删除目标记录用destroy对BelongsToMany两者分别对应删除中间表记录与删除目标记录。多对多带业务属性的中间表利用add/set的[TargetKey, Values]元组一次性写入中间表字段避免二次更新。复合主键源记录sourceKeyValue可传入 JSON 字符串由decodeMultiTargetKey自动解码配合isMultiTargetKey判断分支。批量数据处理大批量遍历关联数据时使用基类的chunk方法按chunkSize分批处理并自动维护offset。事务边界多步关联写入建议显式传入同一个transaction让所有操作在同一事务内提交或回滚。【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价