Can't save multiple documents with same nested property into the same index: error says " object mapping can't be changed from nested to non-nested"

I'm having an issue that I haven't seen on here yet. I am using the .net NEST client.

I have multiple classes (ProductError and BatchError) with the same nested property (Error). When I save these documents individually into an empty/newly-created index, they are mapped correctly. I have done this in unit tests multiple times.

When I index these documents in my web app, both ProductError and BatchError need to be saved to the same index. When I have BatchErrors saved in the index already, I cannot save any Product Errors. I am able to create the Product Error correctly and the Error property is correctly nested (I can view it in the Visual Studio 2017 debugger to check, it's also been unit tested appropriately).

However, I get an error when I try to save the ProductError to the same index that currently has BatchErrors already saved to it.

I haven't been able to figure out why. The exceptions says "object mapping [errors] can't be changed from nested to non-nested", but I know that is not correct because I can construct the document properly, it just won't save to the index.

Also, a potentially important part of information is that if I save a ProductError in the index first, then the same thing happens when I try to save a BatchError.

I'll gladly provide anymore information. Let me know if you need something. I appreciate the help and have been stuck on this for a long time.

Full Error Text

Invalid NEST response built from a unsuccessful low level call on PUT: /unit_test_errors_save/producterror/unittest7
# Audit trail of this API call:
 - [1] BadResponse: Node: / Took: 00:00:00.1160897
# ServerError: ServerError: 400Type: illegal_argument_exception Reason: "object mapping [errors] can't be changed from nested to non-nested"
# OriginalException: System.Net.WebException: The remote server returned an error: (400) Bad Request.
   at System.Net.HttpWebRequest.GetResponse()
   at Elasticsearch.Net.HttpConnection.Request[TReturn](RequestData requestData)
# Request:
<Request stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>
# Response:
<Response stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>

CLASSES

namespace Ilab.Elasticsearch.V5FeedErrors.FeedErrors
{
    [JsonObjectAttribute]
    public class Error
    {
        public string Identifier { get; set; }
        public string ErrorMessage { get; set; }
    }
}


namespace Ilab.Elasticsearch.V5FeedErrors.FeedErrors
{
    [ElasticsearchType]
    public class BatchError : BaseElasticsearchStorableError, IHasMsku
    {
        [Number]
        public long BatchId { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public string BatchName { get; set; }

        [Nested]
        public Error[] Errors { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public List<string> Mskus { get; set; }


        public BatchError()
        {

        }

 

        public BatchError(long userId, string submissionId, Guid correlationId, DateTime timestamp, long batchId, string batchName, Dictionary<string, string> errors, List<string> mskus)
            : base(userId, submissionId, correlationId, timestamp)
        {
            BatchId = batchId;
            BatchName = batchName;
            Mskus = mskus;
            var errorList = new List<Error>();
            if (errors != null)
            {
                foreach (var error in errors)
                {
                    errorList.Add(new Error() { Identifier =  checkIfNull(error.Key), ErrorMessage = checkIfNull(error.Value) });
                }
                this.Errors = errorList.ToArray();
            }
            else
            {
                errorList.Add(new Error() { Identifier = "-", ErrorMessage = "-" });
                this.Errors = errorList.ToArray();
            }
        }

    
        private string checkIfNull(string e)
        {
            if (string.IsNullOrEmpty(e))
            {
                return "-";
            }
            else
            {
                return e;
            }
        }
    }
}
namespace Ilab.Elasticsearch.V5FeedErrors.FeedErrors
{
    [ElasticsearchType]
    public class ProductError : BaseElasticsearchStorableError, IHasMsku
    {
        public long BatchId { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public string BatchName { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public string Asin { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public string Fnsku { get; set; }

        [Nested]
        public Error[] Errors { get; set; }

        [String(Analyzer = "ilab_partial_word_analyzer")]
        public List<string> Mskus { get; set; }

        public ProductError()
        {

        }

        public ProductError(long userId, string submissionId, Guid correlationId, DateTime? timestamp, long batchId, string batchName, string asin, string msku, string fnsku, Dictionary<string, string> errors = null)
           : base(userId, submissionId, correlationId, timestamp)
        {

            BatchId = batchId;
            BatchName = batchName;
            Asin = asin;
            Mskus = new List<string>() { msku };
            Fnsku = fnsku;
            var errorList = new List<Error>();

            if (errors != null)
            {
                foreach (var error in errors)
                {
                    errorList.Add(new Error() { Identifier = checkIfNull(error.Key), ErrorMessage = checkIfNull(error.Value) });
                }
                this.Errors = errorList.ToArray();
            }
            else
            {
                errorList.Add(new Error() { Identifier = "-", ErrorMessage = "-" });
                this.Errors = errorList.ToArray();
            }
        }

  
        private string checkIfNull(string e)
        {
            if (string.IsNullOrEmpty(e))
            {
                return "-";
            }
            else
            {
                return e;
            }
        }
    }

}

What does the mapping for the index look like?

I believe all the config for mapping the index is right here.

  public abstract class BaseElasticSearchClient
    {

        protected IIlabIndexSettings _indexSettings;
        private IConnection _httpConnection;
        private SingleNodeConnectionPool _pool;
        private IlabConnectionSettings _config;

        public BaseElasticSearchClient(IIlabIndexSettings indexSettings)
        {
            _indexSettings = indexSettings;
        }

        public bool IndexExists(string indexName)
        {
            using (var _nestClient = IlabElasticClient.GetNewClient(_indexSettings))
            {
                var exists = _nestClient.IndexExists(indexName).Exists;
                return exists;
            }

        }
        protected bool CreateDocumentType<T>(string indexName) where T : class
        {
            using (var _nestClient = IlabElasticClient.GetNewClient(_indexSettings))
            {
                var name = indexName.ToLower();
                var exists = _nestClient.IndexExists(name).Exists;
                if (!exists)
                {
                    throw new InvalidOperationException(String.Format("The index {0} does not exist!", indexName));
                }
                else
                {
                    var mappingResponse = _nestClient.Map<T>(m => m
                        .AutoMap(2)
                        .Index(name)
                    );
                    if (mappingResponse.Acknowledged)
                        return true;
                }
                return false;
            }

        }
        private CreateIndexRequest GetIndexRequest(string indexName)
        {
            var request = new CreateIndexRequest(indexName);
            request.Settings = new IndexSettings();
            request.Settings.NumberOfReplicas = _indexSettings.NumberOfReplicas;
            request.Settings.NumberOfShards = _indexSettings.NumberOfShards;
            request.Settings.Analysis = new Analysis();

            // Add tokenizers
            if (_indexSettings.CustomTokenizers != null)
            {
                request.Settings.Analysis.Tokenizers = new Tokenizers();
                foreach (var tokenizer in _indexSettings.CustomTokenizers)
                {
                    if (String.IsNullOrWhiteSpace(tokenizer.Name))
                    {
                        throw new InvalidOperationException("A valid tokenizer name must be provided otherwise analyzers won't have a way to reference it!");
                    }
                    request.Settings.Analysis.Tokenizers.Add(tokenizer.Name, tokenizer.Tokenizer);
                }
            }

            // Add custom analyzers
            if (_indexSettings.CustomAnalyzers != null)
            {
                request.Settings.Analysis.Analyzers = new Analyzers();
                foreach (var analyzer in _indexSettings.CustomAnalyzers)
                {
                    if (String.IsNullOrWhiteSpace(analyzer.Name))
                    {
                        throw new InvalidOperationException("A valid analyzer name must be provided for AutoMapping to work during index creation!");
                    }
                    request.Settings.Analysis.Analyzers.Add(analyzer.Name, analyzer.Analyzer);
                }
            }

            var descriptor = new CreateIndexDescriptor(indexName)
                    .Mappings(ms => ms
                        .Map<Ilab.Elasticsearch.V5FeedErrors.FeedErrors.Error>(m => m.AutoMap())
                        .Map<Ilab.Elasticsearch.V5FeedErrors.FeedErrors.BatchError>(m => m.AutoMap())
                        .Map<Ilab.Elasticsearch.V5FeedErrors.FeedErrors.ShipmentError>(m => m.AutoMap())
                        .Map<Ilab.Elasticsearch.V5FeedErrors.FeedErrors.ProductError>(m => m.AutoMap())
                        .Map<Ilab.Elasticsearch.V5FeedErrors.FeedErrors.BoxContentError>(m => m.AutoMap(1))
                    );

            return request;
        }
        protected bool CreateIndex(string indexName)
        {
            using (var _nestClient = IlabElasticClient.GetNewClient(_indexSettings))
            {
                var name = indexName.ToLower();
                var exists = _nestClient.IndexExists(name);
                if (!exists.Exists)
                {


                    var indexRequest = GetIndexRequest(name);
                    var indexResponse = _nestClient.CreateIndex(indexRequest);

                    if (indexResponse.Acknowledged)
                    {
                        return true;
                    }
                    else if (indexResponse.OriginalException != null)
                    {
                        throw indexResponse.OriginalException;
                    }
                }
                return false;
            }

        }
}

What is the mapping for the index if you directly query the Elasticsearch get mapping API?

GET errors_search/_search
{}

^That will return all the documents. I am realiasing the ones I want to be searched into that errors_search alias.

Unless you provide the information I am asking for I am unfortunately not sure I will be able to help. I prefer seeing what is in Elasticsearch as I am not a .NET developer and do not know how the NEST clients maps things.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.