父/子关系示例
在以下的示例中,我们创建了两个均继承自MyDocument
类的实体,分别为:MyParent
和MyChild
。其.NET类型不必是直接的父子关系。可以是普通的实体类,或者MyChild
可以是MyParent
的子类。唯一的要求是它们有一个属性类型为JoinField
的属性。示例代码如下:
public abstract class MyDocument
{
public int Id { get; set; }
public JoinField MyJoinField { get; set; }
}
public class MyParent : MyDocument
{
[Text]
public string ParentProperty { get; set; }
}
public class MyChild : MyDocument
{
[Text]
public string ChildProperty { get; set; }
}
父/子关系映射
在下面的示例中,我们将设置客户端,并为类型指定首选索引和类型名称。从NEST 6开始,我们可以通过DefaultMappingFor<T>
给类型设置一个首选的关系名,如下:
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(connectionPool, new InMemoryConnection())
.DefaultMappingFor<MyDocument>(m => m.IndexName("index"))
.DefaultMappingFor<MyChild>(m => m.IndexName("index"))
.DefaultMappingFor<MyParent>(m => m.IndexName("index").RelationName("parent"));
var client = new ElasticClient(connectionSettings);
通过配置ConnectionSettings
,我们可以将MyParent
和MyChild
的映射关系作为创建索引请求的一部分,如下:
var createIndexResponse = client.Indices.Create("index", c => c
.Index<MyDocument>()
.Map<MyDocument>(m => m
.RoutingField(r => r.Required())
.AutoMap<MyParent>()
.AutoMap<MyChild>()
.Properties(props => props
.Join(j => j
.Name(p => p.MyJoinField)
.Relations(r => r
.Join<MyParent, MyChild>()
)
)
)
)
);
我们为MyParent
和MyChild
两种类型分别调用AutoMap()
来识别.NET的类型。AutoMap()
不会自动设置连接字段映射,因为NEST
不能从你的域中推断出所需的所有关系。
在本例中,我们将MyChild
设置为MyParent
的子节点。.Join()
方法有许多重载,所以在使用时请注意选择需要的重载方法。
以上NEST
配置创建得到的Elasticsearch映射关系如下:
{
"mappings": {
"_routing": {
"required": true
},
"properties": {
"parentProperty": {
"type": "text"
},
"childProperty": {
"type": "text"
},
"id": {
"type": "integer"
},
"myJoinField": {
"type": "join",
"relations": {
"parent": "mychild"
}
}
}
}
}
索引父文档或者子文档
现在我们已经在索引上建立了连接字段映射,我们可以继续为父文档和子文档建立索引。
使用以下三种方式来标记文档得到的结果都是相同的:
var parentDocument = new MyParent
{
Id = 1,
ParentProperty = "a parent prop",
MyJoinField = JoinField.Root<MyParent>()
};
parentDocument = new MyParent
{
Id = 1,
ParentProperty = "a parent prop",
MyJoinField = typeof(MyParent)
};
parentDocument = new MyParent
{
Id = 1,
ParentProperty = "a parent prop",
MyJoinField = "myparent"
};
var indexParent = client.IndexDocument(parentDocument);
第一种方式,我们显式调用了JoinField.Root
将此文档标记为父-子关系的根节点,即MyParent
关系的根节点。第二和第三种是隐式地依赖string
和Type
类型指定父-子关系映射。
得到的映射关系为:
{
"id": 1,
"parentProperty": "a parent prop",
"myJoinField": "myparent"
}
将子文档链接到父文档遵循类似的模式。这里,我们通过从父实例parentDocument
推断id来创建一个链接:
var indexChild = client.IndexDocument(new MyChild
{
MyJoinField = JoinField.Link<MyChild, MyParent>(parentDocument)
});
或者通过简单地指定父文档ID的方式来实现子文档链到父文档:
indexChild = client.IndexDocument(new MyChild
{
Id = 2,
MyJoinField = JoinField.Link<MyChild>(1)
});
{
"id": 2,
"myJoinField": {
"name": "mychild",
"parent": "1"
}
}
父文档和子文档的路由
父节点和它的所有子节点需要存储在同一个分片上,所以你需要指定文档的路由。
在Elasticsearch 6.0以前,你需要使用parent=<parentid>
来指定路由,但在Elasticsearch6.0以后,你可以使用routing=<parentid>
来指定路由。
NEST
提供了一个帮助类来推断正确的路由值,该文档能够找到连接字段并推断正确的父文档,如下示例:
var infer = client.Infer;
var parent = new MyParent {Id = 1337, MyJoinField = JoinField.Root<MyParent>()};
infer.Routing(parent).Should().Be("1337");
var child = new MyChild {Id = 1338, MyJoinField = JoinField.Link<MyChild>(parentId: "1337")};
infer.Routing(child).Should().Be("1337");
child = new MyChild {Id = 1339, MyJoinField = JoinField.Link<MyChild, MyParent>(parent)};
infer.Routing(child).Should().Be("1337");
此示例中,我们只是将实例传递给Routing
,它可以根据实例上的JoinField属性推断正确的路由键。
var indexResponse = client.Index(parent, i => i.Routing(Routing.From(parent)));
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=1337");
同样,当我们索引一个子节点时,我们可以直接将实例传递给Routing
方法,而NEST
将使用已经在child
上指定的父id,示例如下:
indexResponse = client.Index(child, i => i.Routing(Route(child)));
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=1337");
当然,你也可以覆盖默认的推断路由,如下:
indexResponse = client.Index(child, i => i.Routing("explicit"));
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=explicit");
indexResponse = client.Index(child, i => i.Routing(null));
indexResponse.ApiCall.Uri.Query.Should().NotContain("routing");
var indexRequest = new IndexRequest<MyChild>(child) { Routing = Route(child) } ;
indexResponse = client.Index(indexRequest);
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=1337");
需要注意的是,路由是在请求时解析的,而不是在实例化时解析的。在为child
创建索引请求之后,我们更新child
的JoinField
方法如下:
child.MyJoinField = JoinField.Link<MyChild>(parentId: "something-else");
indexResponse = client.Index(indexRequest);
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=something-else");
发表评论
登录用户才能发表评论, 请 登 录 或者 注册