Simple Code
Simple Code

Reputation: 2574

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?

Upvotes: 1

Views: 3015

Answers (2)

Simple Code
Simple Code

Reputation: 2574

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.

Upvotes: 1

Pankaj Sharma
Pankaj Sharma

Reputation: 123

Look likes problem lies here $"ctx._source.productAttributes= params.name;"

productAttributes is an object (nest object) and params.name is a value (string) and that what ES complaints in query response.

Not sure what exactly you wants to do, if your requirement is to update the name for all productAttributes elements you can try this:

        var result = elasticClient.UpdateByQuery<ProductType>(u => u
            .Index("product")
            .Script(ss => ss.Source("for(int i=0; i<ctx._source.productAttributes.size(); i++){HashMap myKV = ctx._source.productAttributes.get(i);myKV.put(params.item.fieldName, params.item.fieldValue);}")
                        .Params(d => d.Add("item", new Dictionary<string, object>()
                        {
                            {"fieldName", "name" },
                            {"fieldValue", "new name" }
                        })).Lang("painless")));

Upvotes: 0

Related Questions