Dr. Roggia
Dr. Roggia

Reputation: 1125

ElasticSearch - Add element inside nested List<T> using NEST 5.x

I have a list inside my ElasticSearch index. How can i add a new element to the list using NEST 2? I have already searched an answer to my problem, but all that i found are answer for the version 1.x, and it doesn't work for me.

MODEL:

public class TicketModel
{
    public Guid Id { get; set; }
    public Guid IdUser { get; set; }
    public Guid IdAuthor { get; set; }
    public Guid? IdAssignedUser { get; set; }
    public Guid? IdAsset { get; set; }
    public Guid? IdOwner { get; set; }
    public DateTime CreationDate { get; set; }
    public DateTime? ClosingDate { get; set; }
    public DateTime? DueDate { get; set; }
    public DateTime? LastDateUserView { get; set; }
    public DateTime? LastDateAssignedUserView { get; set; }
    public string TicketCode { get; set; }
    public string Title { get; set; }
    public int IdCategory { get; set; }
    public int? IdSubcategory { get; set; }
    public short? IdPriority { get; set; }
    public short IdState { get; set; }
    public bool IsNew { get; set; }

    public List<TicketMessageModel> TicketMessageList { get; set; }
}

public class TicketMessageModel
{
    public Guid Id { get; set; }
    public Guid IdTicket { get; set; }
    public Guid IdUserFrom { get; set; }
    public Guid? IdUserTo { get; set; }
    public DateTime? CreationDate { get; set; }
    public DateTime? DeleteDate { get; set; }
    public string Message { get; set; }
    public bool HideToFinalUser { get; set; }
    public byte? MessageType { get; set; }

    public List<TicketMessageFilesModel> MessageFileList { get; set; }
}

public class TicketMessageFilesModel
{
    public Guid Id { get; set; }
    public Guid? IdTicketMessage { get; set; }
    public string FileName { get; set; }
    public string FileTitle { get; set; }
    public string FileOriginalName { get; set; }
    public byte FileType { get; set; }
}

I have tried to find a way out using the other answers, but i got stuck here:

client.Update<ElasticSearchTickets.TicketMessageModel, object>(new DocumentPath<ElasticSearchTickets.TicketMessageModel>(elem.Id), 
                q => q.Script(x => x.Inline("ctx._source.MessageFileList += elem"))./*??*/);

thanks in advance.

EDIT:

Here is the code i tried to insert a new element inside of my list:

ElasticSearchTickets.TicketMessageModel elem = new ElasticSearchTickets.TicketMessageModel()
            {
                CreationDate = message.CreationDate,
                DeleteDate = message.DeleteDate,
                HideToFinalUser = message.HideToFinalUser,
                Id = message.Id,
                IdTicket = message.IdTicket,
                IdUserFrom = message.IdUserFrom,
                IdUserTo = message.IdUserTo,
                Message = message.Message,
                MessageType = message.MessageType,
                MessageFileList = tempList
            };
            var response = client.Update<ElasticSearchTickets.TicketModel, object>(new DocumentPath<ElasticSearchTickets.TicketModel>(elem.IdTicket.ToString()), q => q
                .Script(s => s
                    .Inline("if (ctx._source.TicketMessageList == null) { ctx._source.TicketMessageList = element; } else { ctx._source.TicketMessageList += element; }")
                    .Params(d => d.Add("element", new [] { elem }))
                ));

But now i got a Server Error, because the property "element" has not be declared. How to fix this?

Here is the mapping:

"mappings": {
  "ticket": {
    "properties": {
      "ClosingDate": {
        "type": "date"
      },
      "CreationDate": {
        "type": "date"
      },
      "DueDate": {
        "type": "date"
      },
      "Id": {
        "type": "keyword"
      },
      "IdAsset": {
        "type": "keyword"
      },
      "IdAssignedUser": {
        "type": "keyword"
      },
      "IdAuthor": {
        "type": "keyword"
      },
      "IdCategory": {
        "type": "integer"
      },
      "IdOwner": {
        "type": "keyword"
      },
      "IdPriority": {
        "type": "short"
      },
      "IdState": {
        "type": "short"
      },
      "IdSubcategory": {
        "type": "integer"
      },
      "IdUser": {
        "type": "keyword"
      },
      "IsNew": {
        "type": "boolean"
      },
      "LastDateAssignedUserView": {
        "type": "date"
      },
      "LastDateUserView": {
        "type": "date"
      },
      "TicketCode": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "TicketMessageList": {
        "type": "nested",
        "properties": {
          "CreationDate": {
            "type": "date"
          },
          "DeleteDate": {
            "type": "date"
          },
          "HideToFinalUser": {
            "type": "boolean"
          },
          "Id": {
            "type": "keyword"
          },
          "IdTicket": {
            "type": "keyword"
          },
          "IdUserFrom": {
            "type": "keyword"
          },
          "IdUserTo": {
            "type": "keyword"
          },
          "Message": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "MessageFileList": {
            "properties": {
              "FileName": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "FileOriginalName": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "FileTitle": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              },
              "FileType": {
                "type": "short"
              },
              "Id": {
                "type": "keyword"
              },
              "IdTicketMessage": {
                "type": "keyword"
              }
            }
          },
          "MessageType": {
            "type": "short"
          }
        }
      },
      "Title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }

Upvotes: 2

Views: 2955

Answers (1)

Russ Cam
Russ Cam

Reputation: 125498

Here's a stripped down example

void Main()
{
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    var defaultIndex = "default-index";
    var connectionSettings = new ConnectionSettings(pool)
            .DefaultIndex(defaultIndex);

    var client = new ElasticClient(connectionSettings);

    if (client.IndexExists(defaultIndex).Exists)
        client.DeleteIndex(defaultIndex);

    client.CreateIndex(defaultIndex, c => c
        .Mappings(ms => ms
            .Map<TicketMessageModel>(m => m
                .AutoMap()
                .Properties(p => p
                    .Nested<TicketMessageFilesModel>(n => n
                        .Name(nn => nn.MessageFileList)
                        .AutoMap()
                    )
                )
            )
        )
    );

    var id = "ticketmessage";

    client.Index(new TicketMessageModel
    {
        Id = id,
        MessageFileList = new List<UserQuery.TicketMessageFilesModel>
        {
            new TicketMessageFilesModel { Id = "file1" } 
        }
    });

    client.Update<TicketMessageModel, object>(id, q => q
        .Script("if (ctx._source.messageFileList == null) { ctx._source.messageFileList = elem; } else { ctx._source.messageFileList += elem; }")
        .Params(d => d
            .Add("elem", new[] { new TicketMessageFilesModel { Id = "file2" } })
        )
    );

    var getResponse = client.Get<TicketMessageModel>(id);
}

public class TicketMessageModel
{
    public string Id { get; set; }
    public List<TicketMessageFilesModel> MessageFileList { get; set; }
}

public class TicketMessageFilesModel
{
    public string Id { get; set; }
}

getResponse JSON response is

{
  "_index" : "default-index",
  "_type" : "ticketmessagemodel",
  "_id" : "ticketmessage",
  "_version" : 2,
  "found" : true,
  "_source" : {
    "id" : "ticketmessage",
    "messageFileList" : [ {
      "id" : "file1"
    }, {
      "id" : "file2"
    } ]
  }
}

Some points of interest:

  1. You need to handle the case where there may not be any items in the list by checking if it's null
  2. In order to append to the list, you need to wrap the item that you wish to append in an array, such that the list and array can be appended together.
  3. You need to use the serialized field names as they exist in _source. By default, NEST camel cases POCO property names when serializing to field names in Elasticsearch

EDIT:

It looks like you are working with NEST 5.x against Elasticsearch 5.x; the default scripting language in 5.x is Painless as opposed to Groovy in 2.x and so there are some small subtle differences to the script that must be made.

Here's a version that works on 5.x with Painless

client.Update<TicketMessageModel, object>(id, q => q
    .Script(s => s
        .Inline("if (ctx._source.messageFileList == null) { ctx._source.messageFileList = new ArrayList(); } ctx._source.messageFileList.add(params.elem);")
        .Params(d => d
            .Add("elem", new TicketMessageFilesModel { Id = "file2" })
        )
    )
);

Check out the guide on Painless for more details. You can also use the original example with Groovy too, by specifying .Lang("groovy") inside of .Script() but you would also need to allow inline Groovy scripts to run by adding

script.engine.groovy.inline: true

to the Elasticsearch.yml configuration. Groovy scripts are disabled by default for security reasons.

Upvotes: 3

Related Questions