父/子关系映射

1017 发布于: 2021-04-19 读完约需 4 分钟

父/子关系示例

在以下的示例中,我们创建了两个均继承自MyDocument类的实体,分别为:MyParentMyChild。其.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,我们可以将MyParentMyChild的映射关系作为创建索引请求的一部分,如下:

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>()
                )
            )
        )
    )
);

我们为MyParentMyChild两种类型分别调用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关系的根节点。第二和第三种是隐式地依赖stringType类型指定父-子关系映射。

得到的映射关系为:

{
  "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创建索引请求之后,我们更新childJoinField方法如下:

child.MyJoinField = JoinField.Link<MyChild>(parentId: "something-else");
indexResponse = client.Index(indexRequest);
indexResponse.ApiCall.Uri.Query.Should().Contain("routing=something-else");

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册