
数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载RxDB 是一个运行在多种 JavaScript 运行时上的本地优先local-first数据库它并不提供传统关系型数据库中的 JOIN 操作。本文围绕 population.md 系统讲解如何在 RxDB 中通过 Schema 的ref关键字定义跨集合引用并使用populate()方法与_后缀 getter 快速取回被引用文档帮助你在保持无锁、离线优先架构的同时实现一对一、一对多乃至嵌套的关系建模。读完本文你将掌握 RxDB 关系字段的完整定义方式、两种填充取值技巧以及底层实现与常见错误码的排查方法。为什么需要 Population没有 Join 的引用方案RxDB 的设计哲学中不包含 JOIN 操作——因为 JOIN 会破坏本地优先数据库的简单性与可复制性。但业务中我们依然经常需要“一个文档引用另一个文档”的场景比如一篇文章引用它的作者、一个用户引用他的好友列表。为此 RxDB 提供了population填充机制在 RxSchema 中通过ref关键字声明字段指向某个集合之后即可通过populate()方法或带下划线后缀_的 getter拿到被引用的 RxDocument 实例本身。你可以把引用关系建立在同一集合内如“人类”引用另一个“人类”也可以建立在同一数据库下的不同集合之间如human集合引用human2集合。这一用法与 mongoose 的populate概念一致但对本地优先数据库而言它不需要任何服务器端连接全部在本地完成解析。Schema 中使用 ref 定义关系ref关键字位于字段的 Schema 描述中其值是被引用集合的名称字符串。引用字段的取值必须是被引用文档的主键primaryKey值因此ref字段的类型必须是string或[string, null]允许为空。下面是一个一对一引用的示例每个人类文档通过bestFriend字段引用human集合中的另一个文档。export const refHuman { title: human related to other human, version: 0, primaryKey: name, properties: { name: { type: string, maxLength: 100 }, bestFriend: { ref: human, // 指向名为 human 的集合 // ref 字段的值必须是 string // 或 [string, null] // 即外键 RxDocument 的主键值 type: string } } };若要表达一对多关系只需把字段类型声明为字符串数组即可export const schemaWithOneToManyReference { version: 0, primaryKey: name, type: object, properties: { name: { type: string, maxLength: 100 }, friends: { type: array, ref: human, // ref 声明在数组字段上 items: { type: string // 数组元素为被引用文档的主键 } } } };关于ref的合法性从仓库测试用例 population.test.ts 中可以确认以下事实允许将主键本身作为引用字段历史上曾禁止见 issue #2747 相关注释例如primaryKey: bestFriend且该字段带ref是合法的嵌套对象内部可以声明 ref如foo.bestFriend数组字段的ref既可以声明在数组字段本身上也可以声明在items上两种写法都受支持ref字段的类型必须是字符串若省略type只写refSchema 创建时会直接抛出错误对应“ref-type is no string”的负向测试。用 populate() 方法获取被引用文档基本用法populate()是 RxDocument 实例上的方法接收字段路径作为参数返回一个 Promise解析为被引用的 RxDocument若引用值不存在或为空则解析为null。await humansCollection.insert({ name: Alice, bestFriend: Carol }); await humansCollection.insert({ name: Bob, bestFriend: Alice }); const doc await humansCollection.findOne(Bob).exec(); const bestFriend await doc.populate(bestFriend); console.dir(bestFriend); // RxDocument[Alice]populate()同样支持点号路径填充嵌套字段例如doc.populate(foo.bestFriend)。用 _ 后缀 getter 直接取回除了方法调用RxDB 还提供了一个更简洁的写法在字段名后面加下划线后缀_即可像访问普通属性一样拿到填充结果。该 getter 同样返回 Promise并同样支持嵌套对象上的_访问。await humansCollection.insert({ name: Alice, bestFriend: Carol }); await humansCollection.insert({ name: Bob, bestFriend: Alice }); const doc await humansCollection.findOne(Bob).exec(); const bestFriend await doc.bestFriend_; // 注意字段名末尾的下划线 _ console.dir(bestFriend); // RxDocument[Alice]嵌套引用示例当引用藏在嵌套对象中时_后缀依然有效const myCollection await myDatabase.addCollections({ human: { schema: { version: 0, type: object, properties: { name: { type: string }, family: { type: object, properties: { mother: { type: string, ref: human } } } } } } }); /** * 假设 myDocument 是该集合中的一份文档 */ const mother await myDocument.family.mother_; console.dir(mother); // RxDocument数组引用示例当填充数组字段时返回结果是一个RxDocument 数组且顺序与引用主键数组的顺序完全一致const myCollection await myDatabase.addCollections({ human: { schema: { version: 0, type: object, properties: { name: { type: string }, friends: { type: array, ref: human, items: { type: string } } } } } }); //[在此处插入其他 humans 文档] await myCollection.insert({ name: Alice, friends: [ Bob, Carol, Dave ] }); const doc await humansCollection.findOne(Alice).exec(); const friends await doc.friends_; console.dir(friends); // Array.RxDocument源码解读populate() 的底层实现populate()的核心实现位于 src/rx-document.ts。理解它的执行顺序有助于你排查问题定位 Schema 路径通过getSchemaByObjectPath()从集合的 JSON Schema 中取出目标字段的子 Schema若路径在 Schema 中不存在直接抛出DOC5错误。提取 ref 集合名优先读取字段自身的ref若字段是数组且自身没有ref则回退读取items.ref若两者都不存在抛出DOC6错误。校验目标集合从this.collection.database.collections[ref]中查找被引用集合若未创建则抛出DOC7错误。取值与短路读取当前字段的值若值为空则返回null不会去查数据库。分发查询数组字段调用refCollection.findByIds(value).exec()再按原始主键数组的顺序重新排列结果保证填充顺序与引用顺序一致单值字段调用refCollection.findOne(value).exec()取回单个文档。特别值得注意的是源码中的“先校验、后取值”策略即使在字段值为空falsy的情况下非法路径和非 ref 字段也会被如实暴露为错误而不是被静默吞掉这避免了手写路径时的拼写错误被悄然忽略。_ 后缀 getter 的 Proxy 机制_后缀 getter 之所以能工作是因为 RxDB 对文档嵌套对象使用 Proxy 包装见 src/rx-document.ts 的get拦截逻辑。当访问的属性名以_结尾时Proxy 会截掉下划线并调用doc.populate()若以$结尾则返回可观察的流observable普通属性则直接返回原始值。也就是说doc.bestFriend_与doc.populate(bestFriend)在语义上是等价的。错误码速查与 population 相关的错误码定义在 error-messages.tsdev-mode 插件开启后会在控制台给出更友好的说明错误码含义修复建议DOC5populate()的路径在 Schema 中不存在检查字段名与 Schema 定义DOC6该字段存在但未声明ref为字段添加ref属性DOC7Schema 中引用的集合不存在于当前数据库创建被引用的集合边界条件与最佳实践结合仓库中的单元测试以下行为值得注意跨集合引用ref可以指向任何同数据库下已创建的集合。测试 population.test.ts 展示了human引用human2且主键作为 ref 值的场景填充结果会来自正确的目标集合。multiInstance: false下同样可用历史上 population 在关闭多实例模式时存在缺陷issue #222当前版本已修复测试覆盖了该场景。数组顺序保证当两份文档以不同顺序引用同一组主键时填充结果各自保持自己的原始顺序相关回归测试见 population.test.ts。注意这个行为依赖populate()内部的按序重组逻辑不要依赖findByIds的 Map 迭代顺序。空值安全引用字段未赋值或值不存在时populate()返回null可放心在逻辑中做空值判断。Schema 测试工具仓库的 schemas.ts 提供了带ref的refHuman标准 Schemahumans-collection.ts 提供了createRelated/createRelatedNested辅助函数可供你在自己的测试中参考如何构造带关系的集合。延伸阅读RxDocument掌握文档实例的完整 APIRxCollection了解集合的创建与查询方式RxSchema深入学习 Schema 定义规则transactions-conflicts-revisions.md理解本地优先场景下多文档一致性的取舍这也是 RxDB 不提供 JOIN 的深层原因赞分享数据库NoSQL嵌入式数据库实时数据库【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址https://gitcode.com/gh_mirrors/rx/rxdb点击查看免费下载相关推荐在 Flutter 应用中使用 RxDB 作为本地优先数据库的完整指南在 Flutter 应用中使用 RxDB 作为本地优先数据库的完整指南 在 Flutter 移动应用开发中选择合适的数据库直接影响应用的性能、可扩展性与用户体数据库NoSQL嵌入式数据库实时数据库暗黑破坏神2存档编辑器可视化修改工具彻底革新游戏体验暗黑破坏神2存档编辑器可视化修改工具彻底革新游戏体验 你是否曾因繁琐的十六进制编辑而放弃修改暗黑2存档是否梦想过像游戏设计师那样自由定制角色属性、装备和游戏数据库NoSQL嵌入式数据库实时数据库F3D如何用极简工具实现专业级3D模型预览的完整指南F3D如何用极简工具实现专业级3D模型预览的完整指南 你是否曾为复杂的3D软件安装包而烦恼是否在寻找一款能快速预览各种格式3D模型的轻量级工具今天我将为你数据库NoSQL嵌入式数据库实时数据库上一篇告别串行等待GPT计算机助手v0.66.0异步任务处理革命下一篇3分钟掌握fish-shell安全合规从配置到审计实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考