Elasticsearch.Nest 教程系列 4-5 映射:Parent/Child relationships | 子父级关系映射
本系列博文是“伪”官方文档翻译(更加本土化),并非完全将官方文档进行翻译,而是在查阅、测试原始文档并转换为自己真知灼见后的“准”翻译。有不同见解 / 说明不周的地方,还请海涵、不吝拍砖 :)
官方文档见此:https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html
本系列对应的版本环境:ElasticSearch@7.3.1,NEST@7.3.1,IDE 和开发平台默认为 VS2019,.NET CORE 2.1
如果把 官方示例 放在你本地进行测试,会发现没有问题,但当你连接测试 ES 服务器的时候,就会报各种错误:
- 这是因为官方使用了 InMemoryConnection ,所有请求并不会真实发送到测试 ES 服务器,规避掉了不少现实问题。以下示例基于官方文档进行了修正,使用本地搭建测试 ES 服务器。
父子关系的数据结构,在日常开发过程中,使用还是比较平凡的:
-
在 ES 6.x 之前,你可以在一个索引中包含多种 type(类型)。通过给定类型的特殊 _parent 字段映射,可以创建 1对N 关系 的父-子文档。
- 通过这种方式在索引子项时,你需要传递一个 _parent id 作为路由键(需要确保父项、其子项及所有祖先都保存在同一个分片上)。
-
但从 ES 6.x 开始,type 不再支持多类型,固定 _type=_doc。(另外在 ES5 的时候,ES 的 index 相当于 DB,_type 相当于 DB 中的表名,但从 ES7 开始,ES 的 index 相当于 DB中的表,_type 固定为 _doc)
嵌套对象和父子结构文档的差异对比和使用建议:
Nested Object | Parent/Child | |
---|---|---|
优点 | 文档存储再一起,读取性能高 | 父子文档可以独立更新 |
缺点 | 更新嵌套的子文档时,需要更新整个文档 | 需要额外的内存维护关系。读取性能相对差 |
适用场景 | 子文档偶尔更新,以查询为主 | 子文档更新频繁 |
既然索引不再允许将不同 _type 类型存储在同一索引中,那么如何创建父联接?
通过 JoinField 属性
唯一的要求是作为子父级关系的类必须要具有数据类型为 JoinField 的属性。通过该属性,在请求的时候,可以生成关联类型。
示例:
1 | public abstract class MyDocument |
-
MyParent 和 MyChild 均继承自 MyDocument,且注意 MyDocument.ParentChildRelation 的数据类型为 JoinField (该类型是 Nest 库提供的数据类型) 。
- 这里,parentChildRelation 即为 Join 类型的名称。
将 MyChild 和 MyParent 设置相同的索引和默认文档类型,以确保它们在同一个索引中:
-
需要将 MyDocument,MyChild 和 MyParent 的索引名称都指定为 “index”确保它们在同一个索引中。
-
DefaultMappingFor 提供了 RelationName ,在 MyParent 上 指定子父级的关联名称为 “parent”。
1 | public ElasticSearchClient(ElasticSearchSettings esSettings) |
通过设置 ConnectionStrings,可以将 MyParent 和 MyChild 映射为索引的一部分,并需要确保子父级文档在同一个分片上:
-
在创建“索引”的时候,通过将“_route”的 required 设置为 true,确保子父级文档在同一个分片上。
- 在同一个分片确保了 JOIN 的查询性能。
- 关于 Nest 中的路由推断,见此博文:NEST 教程系列 9-8 转换:Routing Inference | 路由推断
1 | var createIndexResponse = client.Indices.Create("index", c => c |
-
因为 NEST 的 AutoMap 方法不会自动配置 JoinField 映射,所以你需要手动装配。
-
以上装配方式将 MyChild 设置为 MyParent 的子集。.Join() 方法有许多重载,你可以按需进行选择。
以上命令相当于执行了下面的 ES 命令:
1 | PUT /index |
-
在 ConnectionString 上设置映射的时候,把 MyParent 的关系名称设置为了“parent”。稍后在执行强类型的 has_child 和 has_parent 查询时,这也很方便。
添加父、子文档到 ES 中
添加父文档到 ES 中
通过 MyParent 的关系名称(“parent”)标记文档,以下三种方式都是等效的:
1 | var parentDocument = new MyParent |
最终生成的请求命令 为:
1 | PUT /index/_doc/1?routing=1 |
添加子文档到 ES 中
在添加子文档的时候必须指定它的父文档id:
-
使用 route 参数保证父子文档被分配到相同的分片上。
依葫芦画瓢,将子文档连接到它的父文档上。这里直接使用上面的 parentDocument 对象 来创建连接:
1 | /* 同样这里不能使用官方通过 IndexDocument 来索引文档的例子 |
生成的请求命令为:
1 | PUT /index/_doc/10002?routing=1 |
最终在 index 索引中,生成的父子文档内容如下:
1 | { |
ElasticClient.Inferrer
-
在索引的时候,需要指定 routing
-
在过去,你需要在请求中使用 parent=
的方式(在 ES 5.x),或者 routing= 的方式(在 ES 6.x)来路由父子文档,在 Nest 7 中, Nest 提供了一个帮助类来推断正确的路由值,它会根据 JoinField 属性来推断父类。
1 | var infer = _client.Infer; |
通过 Routing 方法来改写上面索引父子文档的例子:
1 | //索引父文档 |
最终添加的文档内容为:
1 | { |