dana
dana

Reputation: 18155

PUT Mapping Using High Level Nest Client and AutoMap

I am creating Elasticsearch indices using the following code snippet:

ICreateIndexResponse createIndexResponse = elasticClient.CreateIndex(IndexName, c => c
    .Mappings(ms => ms
        .Map<Document>(m => m.AutoMap())
    )
);

The Document class is a POCO with attribute mappings.

I would like the ability to add fields to my mapping. This looks to be possible using the Put Mapping API:

PUT my_index 
{
  "mappings": {
    "_doc": {
      "properties": {
        "name": {
          "properties": {
            "first": {
              "type": "text"
            }
          }
        },
        "user_id": {
          "type": "keyword"
        }
      }
    }
  }
}

PUT my_index/_mapping/_doc
{
  "properties": {
    "name": {
      "properties": {
        "last": { 
          "type": "text"
        }
      }
    },
    "user_id": {
      "type": "keyword",
      "ignore_above": 100 
    }
  }
}

https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html

Notice that the first PUT is creating the index and mapping. The second PUT is adding and modifying fields. I want to be able to execute the second PUT.

The ideal scenario would be to add properties to my Document class, call AutoMap, and use the client to call the PUT Mapping API. New properties would be added to my mapping and properties that previously existed were updated/ignored as is appropriate.

Is this possible? Should I be calling the CreateIndex method again with certain parameters?

Upvotes: 0

Views: 3053

Answers (1)

Russ Cam
Russ Cam

Reputation: 125538

The Put Mapping API is exposed on the client as .Map<T>

var client = new ElasticClient();

var putMappingResponse = client.Map<Document>(m => m
    .AutoMap()
);

This is going to automap all the properties of Document. I believe Elasticsearch will simply no-op for those mappings that already exist, and add the new mappings.

If you wanted to send only those properties that are not yet mapped, that would be possible by getting the automapped properties of Document, retrieving the mappings from the index, excepting the latter from the former, then sending those with .Map<T>(). Something like

var defaultIndex = "properties_example";
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

var settings = new ConnectionSettings(pool)
    .DefaultIndex(defaultIndex);

var client = new ElasticClient(settings);

if (!client.IndexExists(defaultIndex).Exists)
{
    var createIndexResponse = client.CreateIndex(defaultIndex, c => c
        .Mappings(m => m
            .Map<Document>(mm => mm.AutoMap())
        )
    );
}

var properties = new PropertyWalker(typeof(Document), null).GetProperties();

// will use the index inferred for Document, or the default index if none
// specified. Can specify an index on this call if you want to  
var getMappingResponse = client.GetMapping<Document>();

var indexedMappings = getMappingResponse
    // Use the index name to which the call was made.
    .Indices[defaultIndex]
    .Mappings[typeof(Document)]
    .Properties;

var propertiesToIndex = new Dictionary<PropertyName, IProperty>();  
foreach(var property in properties)
{
    if (!indexedMappings.ContainsKey(property.Key))
    {
        propertiesToIndex.Add(property.Key, property.Value);
    }
}

// map new properties only if there are some to map
if (propertiesToIndex.Any())
{
    var request = new PutMappingRequest<Document>()
    {
        Properties = new Properties(propertiesToIndex)
    };

    var putMappingResponse = client.Map(request);
}

Upvotes: 3

Related Questions