2

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
          }
        }
      }
    }
3
  • what does the mapping look like? Is TicketMessageList mapped as an object type or nested type? Commented Mar 14, 2017 at 19:16
  • Also, the mapping for MessageFileList Commented Mar 15, 2017 at 3:56
  • @RussCam TicketMessageList is a nested type, MessageFileList is an object instead. Commented Mar 16, 2017 at 9:58

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Look at my answer, your code seems working but it doesnt recognize my property. I have updated my question
@FrancescoRoggia You need to adapt the example to your use case; there are two things that I can see that may be causing issues: 1. You have a Pascal cased property name in your script - see point 3 above and 2. It looks like you are using NEST 5.x and not NEST 2.x - NEST 5.x is only compatible with Elasticsearch 5.x. Can you provide more detail on the server error?
{Type: illegal_argument_exception Reason: "failed to execute script" CausedBy: "Type: script_exception Reason: "compile error" CausedBy: "Type: illegal_argument_exception Reason: "Variable [element] is not defined."""} The problem is inside of Params, because i update the right element inside of _source, but the code doesn't recognize my parameter
@FrancescoRoggia I've added an example for NEST 5.x running against Elasticsearch 5.x; the default scripting language is Painless in 5.x so there are some differences in the script

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.