Search code examples
c#elasticsearchnest

How to update nested objects using Nest Elasticsearch?


I have product index which for simplicity has two fields Id and ProductAttributes as nested object defined as following:

public class ProductType
{
    public Guid Id { get; set; }

    public List<ProductAttribute> ProductAttributes { get; set; }
 }

public class ProductAttribute
{
    public Guid Id { get; set; }

    public string Name { get; set; }

    public string Value { get; set; }
}

And the following mapping:

elasticClient.CreateIndex("product", i => i
       .Settings(s => s
                 .NumberOfShards(2)
                 .NumberOfReplicas(0)
                 )
                 .Mappings(m => m
                   .Map<ProductType>(map => map
                         .AutoMap()
                         .Properties(p => p
                          .Nested<ProductAttribute>(n => n
                            .Name(c => c.ProductAttributes)
                            .AutoMap()
                            .Properties(nc => nc
                               .Keyword(t => t
                                   .Name(nn => nn.Name)
                                   )
                              .Keyword(t => t
                                .Name(nn => nn.Value)
                             )
                  )
             )

Now I am trying to update name field inside nested object and I have tried implementing that using scripted update as following:

        var scriptParams = new Dictionary<string, object>
                            {
                                {"name", "new name"}
                            };

        var result = elasticClient.UpdateByQuery<ProductType>(u => u
                              .Script(sn => sn
                                   .Inline(
                                          $"ctx._source.productAttributes= params.name;" 
                                      )
                                  .Params(scriptParams)
                              )
                              .Conflicts(Conflicts.Proceed)
                              .Refresh(true)
                          );

But using the above query I couldn't update the nested object, could you please tell how can I update nested object using _update_by_query api using nest ES?


Solution

  • Finally I have found how to update name property for only specific nested objects depending on their id as following:

    var result = elasticClient.UpdateByQuery<ProductType>(u => u
                      .Query(q => q
                            .Nested(n => n
                              .Path(Infer.Field<ProductType>(ff => ff.ProductAttributes))
                              .Query(nq => nq
                                  .Term(Infer.Field<ProductType>(ff => ff.ProductAttributes.First().Id), productAttributeId)
                              )
                            )
                      )
                      .Script(ss => ss.Inline("if (ctx._source.productAttributes != null){for (item in ctx._source.productAttributes){if (item.id == params.id) {item.name = params.name;}}}")
                         .Params(new Dictionary<string, object>()
                         {
                             {"id", productAttributeId},
                             {"name", productAttributeName}
                         }).Lang("painless")
                      )
                      .Conflicts(Conflicts.Proceed)
                      .Refresh(true)
                  );
    

    And here the generated query :

     POST product/producttype/_update_by_query?conflicts=proceed&refresh=true 
    {
      "query": {
        "bool": {
          "must": [
            {
              "nested": {
                "query": {
                  "term": {
                    "productAttributes.id": {
                      "value": "563243f0-8fbb-4adf-a78d-1339e5971a43"
                    }
                  }
                },
                "path": "productAttributes"
              }
            }
          ]
        }
      },
      "script": {
        "params": {
            "id":"563243f0-8fbb-4adf-a78d-1339e5971a43",
            "name": "CPU"
        },
        "lang": "painless",
        "inline": "if (ctx._source.productAttributes != null){for (item in ctx._source.productAttributes){if (item.id == params.id) {item.name = params.name;}}}"
      }
    }
    

    So what does the above query do:

    It first searches for products which have productAttribute with 563243f0-8fbb-4adf-a78d-1339e5971a43 id and then it iterates over productAttributes nested objects to update only attributes with that id and then re-indexes the document again.

    I hope my answer help others facing problems updating nested objects in Elasticsearch.