ES NEST — Как создать динамические объекты индекса и массового индексирования с геометрией (geo_point или geo_shape)?

У меня есть набор данных, для которого я заранее не знаю или не знаю тип, а также количество свойств или их тип.

При выполнении я получаю для этого набора данных схему набора данных, содержащую имена, типы и некоторые флаги для свойств.

Для свойств геометрии у меня есть их представление GeoJson, хранящееся в виде строки, и у меня есть несколько флагов (isGeoShape, isGeoPoint), которые указывают тип свойства ES.

Я также использую NetTopologySuite, если необходимо разобрать эти GeoJson на фактические объекты Geometry, но я предпочитаю не выполните этот дополнительный синтаксический анализ и вместо этого используйте строки GeoJson.

class DatasetSchema {
    List<DatasetField> Fields;
}

class DatasetField { 
    string Name;
    Type DataType;
    bool isGeoShape;
    bool isGeoPoint;
}

Вопросы:

  1. Как я могу создать такой индекс ES с неизвестной/динамической схемой сопоставления с высокоуровневым клиентом NEST с этими свойствами геометрии?

  2. Как я могу массово индексировать эти документы с помощью высокоуровневого клиента NEST с помощью Bulk или BulkAll API с такими свойствами геометрии?

Я видел здесь и здесь, что массовое индексирование может быть выполнено с помощью BulkDescriptor:

dynamic obj = new System.Dynamic.ExpandoObject();
    // ….
var descriptor = new BulkDescriptor();
foreach (var doc in values)
{
    descriptor.Index<object>(i => i
        .Index("abc")
        .Id((Id)doc.Id)
        .Document((object)doc));
}
client.Bulk(descriptor);

Тем не менее, мне любопытно, как следует относиться к типам геометрии?

Большое спасибо! Любые мысли или предложения приветствуются!


person Florin Vîrdol    schedule 10.06.2021    source источник


Ответы (1)


Динамические шаблоны хорошо подходят для вашего варианта использования, эта функция дает вам отличный способ контролировать, как elasticsearch отображает вашу динамическую схему данных.

Вы можете использовать сопоставление. параметр и тип поля управления на основе имени поля. Если экземпляр DatasetField имеет IsGeoPoint, установленный в true, мы можем добавить к имени поля elasticsearch префикс GeoPoint и настроить динамический шаблон для создания поля goe_point для имен с префиксом GeoPoint.

{
    "mappings": {
        "dynamic_templates": [{
                "geo_shape": {
                    "match": "GeoShape*",
                    "mapping": {
                        "type": "geo_shape"
                    }
                }
            }, {
                "geo_point": {
                    "match": "GeoPoint*",
                    "mapping": {
                        "type": "geo_point"
                    }
                }
            }
        ]
    }
}

Вот пример приложения C#, показывающего его в действии.

class Program
{
    static async Task Main(string[] args)
    {
        string indexName = "my_index";
        var connectionSettings = new ConnectionSettings(new Uri("http://localhost:9200"));
        connectionSettings.DefaultIndex(indexName);
        var elasticClient = new ElasticClient(connectionSettings);

        await elasticClient.Indices.DeleteAsync(indexName);
        //create index mapping with two dynamic templates,
        //based on field suffix elasticsearch will map field to specific type
        var indexResponse = await elasticClient.Indices.CreateAsync(indexName, d => d
            .Map(map => map
                .DynamicTemplates(dt => dt
                    .DynamicTemplate("geo_shape", gs => gs.Match("GeoShape*").Mapping(m => m.GeoShape(s => s)))
                    .DynamicTemplate("geo_point", gs => gs.Match("GeoPoint*").Mapping(m => m.GeoPoint(p => p)))
                )));

        //some same data matching your schema
        var data = new List<DatasetField>
        {
            new () { Name = "Field1", IsGeoPoint = true },
            new () { Name = "Field2", IsGeoShape = true },
        };

        var document = new EsDocument();
        foreach (var datasetField in data)
        {
            //if the field is of type geo shape, prefix field name with GeoShape,
            //geo_shape dynamic template will match field name and will create geo_point type for it
            if (datasetField.IsGeoShape)
            {
                document.Add($"GeoShape{datasetField.Name}", new PointGeoShape(new GeoCoordinate(0, 0)));
            }
            //if the field is of type geo point, prefix field name with GeoPoint,
            //geo_point dynamic template will match field name and will create geo_shape type for it
            if (datasetField.IsGeoPoint)
            {
                document.Add($"GeoPoint{datasetField.Name}", new GeoLocation(0, 0));
            }
        }

        var response = await elasticClient.IndexDocumentAsync(document);
    }

    //this class is just an alias to dictionary
    class EsDocument : Dictionary<string,object>{}

    class DatasetField
    {
        public string Name { get; set; }
        public bool IsGeoShape { get; set; }
        public bool IsGeoPoint { get; set; }
    }
}

Это создаст следующее отображение elasticsearch

{
    "my_index": {
        "mappings": {
            "dynamic_templates": [{
                    "geo_shape": {
                        "match": "GeoShape*",
                        "mapping": {
                            "type": "geo_shape"
                        }
                    }
                }, {
                    "geo_point": {
                        "match": "GeoPoint*",
                        "mapping": {
                            "type": "geo_point"
                        }
                    }
                }
            ],
            "properties": {
                "GeoPointField1": {
                    "type": "geo_point"
                },
                "GeoShapeField2": {
                    "type": "geo_shape"
                }
            }
        }
    }
}

Когда дело доходит до массовой индексации документов, проще всего использовать метод расширения IndexManyAsync.

await elasticClient.IndexManyAsync(new List<EsDocument>());

Также ознакомьтесь с этим сообщением в блоге подробное описание индексации нескольких документов. Проверьте раздел «Несколько документов».

ОБНОВЛЕНИЕ: добавьте новый динамический шаблон в сопоставление с существующими динамическими шаблонами.

var map = (await elasticClient.Indices.GetMappingAsync<EsDocument>()).Indices["your_index_name"];
var dynamicTemplates = map.Mappings.DynamicTemplates;
//add new
dynamicTemplates.Add(new KeyValuePair<string, IDynamicTemplate>());
await elasticClient.Indices.PutMappingAsync(new PutMappingRequest("your_index_name") { DynamicTemplates = dynamicTemplates });
person Rob    schedule 10.06.2021
comment
Спасибо, @Роб! Тем не менее, знаете ли вы, как условно/программно добавить/определить dynamicTemplate на основе некоторых соглашений из моей схемы входных данных? - person Florin Vîrdol; 29.06.2021
comment
Да, вы можете использовать метод PutMapping, например await elasticClient.Indices.PutMapping<EsDocument>(m => m.DynamicTemplates());, для обновления и добавления новых динамических шаблонов в существующее сопоставление в соответствии с вашими условиями. - person Rob; 29.06.2021
comment
Отлично, звучит как то, что мне нужно! Тем не менее, есть ли способ вызвать PutMapping для определенного индекса по имени? Я вижу PutMappingAsync(IPutMappingRequest request, CancellationToken ct = default); и PutMappingAsync<TDocument>(Func<PutMappingDescriptor<TDocument>, IPutMappingRequest> selector, CancellationToken ct = default); - person Florin Vîrdol; 29.06.2021
comment
Конечно, вы можете указать индекс с Index() частью дескриптора, например await elasticClient.Indices.PutMapping<EsDocument>(m => m.Index(your_index_name_goes_here).DynamicTemplates()). - person Rob; 29.06.2021
comment
Например: я создаю индекс с двумя динамическими шаблонами. После вызова PutMappingAsync с одним динамическим шаблоном начальные 2 шаблона заменяются последним. В результате вы получите все 3 динамических шаблона. есть идеи? - person Florin Vîrdol; 29.06.2021
comment
Смотрите вашу точку зрения, проверьте мое обновление. Думайте, что единственный способ — получить сопоставление для индекса, добавить новый динамический шаблон и отправить обратно старый + новый. - person Rob; 29.06.2021