【问题标题】:ElasticSearch Nest Insert/UpdateElasticSearch 嵌套插入/更新
【发布时间】:2016-12-25 19:32:56
【问题描述】:

我使用以下查询在弹性中创建了一个索引:

PUT public_site
{
  "mappings": {
    "page": {
      "properties": {
        "url": {
          "type": "string"
        },
        "title":{
          "type": "string"
        },
        "body":{
          "type": "string"
        },
        "meta_description":{
          "type": "string"
        },
        "keywords":{
          "type": "string"
        },
        "category":{
          "type": "string"
        },
        "last_updated_date":{
          "type": "date"
        },
        "source_id":{
        "type":"string"
        }
      }
    }
  }
}

我想使用 .net NEST 库将文档插入此索引。我的问题是 .net 更新方法的签名对我来说没有任何意义。

client.Update<TDocument>(IUpdateRequest<TDocument,TPartialDocument>)

Java 库对我来说更有意义:

UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index("index");
updateRequest.type("type");
updateRequest.id("1");
updateRequest.doc(jsonBuilder()
        .startObject()
            .field("gender", "male")
        .endObject());
client.update(updateRequest).get();

在 NEST 中,TDocumentTPartialDocument 类来自哪里? 我制作的这些 C# 类是否代表我的索引?

【问题讨论】:

    标签: elasticsearch nest


    【解决方案1】:

    TDocumentTPartialDocument 是 POCO 类型的泛型类型参数,

    • 在 Elasticsearch (TDocument) 中表示一个文档和
    • 执行部分更新时在 Elasticsearch (TPartialDocument) 中表示文档的一部分。

    在完全更新的情况下,TDocumentTPartialDocument 可能指的是同一个具体的 POCO 类型。让我们看一些示例来演示。

    让我们使用您在上面定义的映射创建一个索引。首先,我们可以使用 POCO 类型来表示一个文档

    public class Page
    {
        public string Url { get; set; }
    
        public string Title { get; set; }
    
        public string Body { get; set; }
    
        [String(Name="meta_description")]
        public string MetaDescription { get; set; }
    
        public IList<string> Keywords { get; set; }
    
        public string Category { get; set; }
    
        [Date(Name="last_updated_date")]
        public DateTimeOffset LastUpdatedDate { get; set; }
    
        [String(Name="source_id")]
        public string SourceId { get; set; }
    }
    

    默认情况下,当 NEST 序列化 POCO 属性时,它使用驼峰命名约定。因为您的索引具有某些属性的蛇形外壳,例如"last_updated_date",我们可以覆盖 NEST 将这些序列化为使用属性的名称。

    接下来,让我们创建要使用的客户端

    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    var pagesIndex = "pages";
    var connectionSettings = new ConnectionSettings(pool)
            .DefaultIndex(pagesIndex)
            .PrettyJson()
            .DisableDirectStreaming()
            .OnRequestCompleted(response =>
                {
                    // log out the request
                    if (response.RequestBodyInBytes != null)
                    {
                        Console.WriteLine(
                            $"{response.HttpMethod} {response.Uri} \n" +
                            $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}");
                    }
                    else
                    {
                        Console.WriteLine($"{response.HttpMethod} {response.Uri}");
                    }
    
                    Console.WriteLine();
    
                    // log out the response
                    if (response.ResponseBodyInBytes != null)
                    {
                        Console.WriteLine($"Status: {response.HttpStatusCode}\n" +
                                 $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" +
                                 $"{new string('-', 30)}\n");
                    }
                    else
                    {
                        Console.WriteLine($"Status: {response.HttpStatusCode}\n" +
                                 $"{new string('-', 30)}\n");
                    }
                });
    
    var client = new ElasticClient(connectionSettings);
    

    连接设置的配置方式有助于开发;

    1. DefaultIndex() - 默认索引已配置为 "pages"。如果没有在请求中传递明确的索引名称,并且无法为 POCO 推断出索引名称,则将使用默认索引。
    2. PrettyJson() - 美化(即缩进)json 请求和响应。这对于查看 Elasticsearch 发送和接收的内容很有用。
    3. DisableDirectStreaming() - NEST 默认将 POCO 序列化为请求流并反序列化响应流中的响应类型。禁用此直接流式传输将缓冲内存流中的请求和响应字节,允许我们在OnRequestCompleted() 中注销它们
    4. OnRequestCompleted() - 在收到响应后调用。这使我们能够在开发过程中注销请求和响应。

    2、3 和 4 在开发过程中很有用,但会带来一些性能开销,因此您可能决定不在生产环境中使用它们。

    现在,让我们使用页面映射创建索引

    // delete the index if it exists. Useful for demo purposes so that
    // we can re-run this example.
    if (client.IndexExists(pagesIndex).Exists)
        client.DeleteIndex(pagesIndex);
    
    // create the index, adding the mapping for the Page type to the index
    // at the same time. Automap() will infer the mapping from the POCO
    var createIndexResponse = client.CreateIndex(pagesIndex, c => c
        .Mappings(m => m
            .Map<Page>(p => p
                .AutoMap()
            )
        )
    );
    

    Take a look at the automapping documentation for more details around how you can control mapping for POCO types

    索引一个新的页面类型很简单

    // create a sample Page
    var page = new Page
    {
        Title = "Sample Page",
        Body = "Sample Body",
        Category = "sample",
        Keywords = new List<string>
        {
            "sample",
            "example", 
            "demo"
        },
        LastUpdatedDate = DateTime.UtcNow,
        MetaDescription = "Sample meta description",
        SourceId = "1",
        Url = "/pages/sample-page"
    };
    
    // index the sample Page into Elasticsearch.
    // NEST will infer the document type (_type) from the POCO type,
    // by default it will camel case the POCO type name
    var indexResponse = client.Index(page);
    

    如果文档不存在,索引文档将创建该文档,如果存在则覆盖现有文档。 Elasticsearch has optimistic concurrency control 可用于控制其在不同条件下的行为方式。

    我们可以使用Update 方法更新文档,但首先需要了解一些背景知识。

    我们可以通过指定 index、type 和 id 从 Elasticsearch 获取文档。 NEST 使这变得稍微容易一些,因为我们可以从 POCO 中推断出所有这些。创建映射时,我们没有在 POCO 上指定 Id 属性;如果NEST 看到一个名为Id 的属性,它会将其用作文档的 id,但因为我们没有,所以这不是问题,因为 Elasticsearch 将为文档生成一个 id 并将其放入文档元数据中.然而,因为文档元数据与源文档是分开的,所以这会使 POCO 类型的文档建模变得有点棘手(但并非不可能);对于给定的响应,我们将可以通过元数据访问文档的 id,并通过_source 字段访问源。我们可以在应用程序中将 id 与我们的源结合起来。

    解决此问题的更简单方法是在 POCO 上设置一个 ID。我们可以在 POCO 上指定一个 Id 属性,这将用作文档的 id,但如果我们不想调用属性,也不必调用属性 Id,如果我们不想这样做,我们需要告诉 NEST 哪个属性代表 id。这可以通过一个属性来完成。假设 SourceIdPage 实例的唯一 ID,请使用 ElasticsearchTypeAttribute IdProperty 属性来指定它。也许我们不应该也分析这个字符串而是逐字索引它,我们也可以通过属性上的Index属性来控制它

    [ElasticsearchType(IdProperty = nameof(SourceId))]
    public class Page
    {
        public string Url { get; set; }
    
        public string Title { get; set; }
    
        public string Body { get; set; }
    
        [String(Name="meta_description")]
        public string MetaDescription { get; set; }
    
        public IList<string> Keywords { get; set; }
    
        public string Category { get; set; }
    
        [Date(Name="last_updated_date")]
        public DateTimeOffset LastUpdatedDate { get; set; }
    
        [String(Name="source_id", Index=FieldIndexOption.NotAnalyzed)]
        public string SourceId { get; set; }
    }
    

    有了这些,我们需要像以前一样重新创建索引,以便这些更改反映在映射中,并且 NEST 可以在索引Page 实例时使用此配置。

    现在,回到更新 :) 我们可以从 Elasticsearch 获取一个文档,在应用程序中更新它,然后重新索引它

    var getResponse = client.Get<Page>("1");
    
    var page = getResponse.Source;
    
    // update the last updated date 
    page.LastUpdatedDate = DateTime.UtcNow;
    
    var updateResponse = client.Update<Page>(page, u => u.Doc(page));
    

    第一个参数是我们想要获取的文档的 id,它可以由 NEST 从Page 实例中推断出来。由于我们将整个文档传回此处,我们可以只使用.Index() 而不是Update(),因为我们正在更新所有字段

    var indexResponse = client.Index(page);
    

    但是,由于我们只想更新LastUpdatedDate,因此必须从 Elasticsearch 获取文档,在应用程序中更新它,然后将文档发送回 Elasticsearch 是很多工作。我们可以只将更新后的LastUpdatedDate 发送到 Elasticsearch,而不是使用 partial 文档。 C# 匿名类型在这里非常有用

    // model our partial document with an anonymous type. 
    // Note that we need to use the snake casing name
    // (NEST will still camel case the property names but this
    //  doesn't help us here)
    var lastUpdatedDate = new
    {
        last_updated_date = DateTime.UtcNow
    };
    
    // do the partial update. 
    // Page is TDocument, object is TPartialDocument
    var partialUpdateResponse = client.Update<Page, object>("1", u => u
        .Doc(lastUpdatedDate)
    );
    

    如果需要使用RetryOnConflict(int),这里可以使用乐观并发控制

    var partialUpdateResponse = client.Update<Page, object>("1", u => u
        .Doc(lastUpdatedDate)
        .RetryOnConflict(1)
    );
    

    通过部分更新,Elasticsearch 将获取文档,应用部分更新,然后索引更新的文档;如果文档在获取和更新之间发生变化,Elasticsearch 将根据 RetryOnConflict(1) 再次重试。

    希望有帮助:)

    【讨论】:

      猜你喜欢
      • 2015-11-14
      • 1970-01-01
      • 2021-06-14
      • 2022-08-10
      • 2018-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-01
      相关资源
      最近更新 更多