Elasticsearch(八)【NEST高级客户端--Mapping映射】

xiaoxiao2021-02-28  87

要使用NEST与Elasticsearch进行交互,我们需要能够将我们的解决方案中的POCO类型映射到存储在Elasticsearch中的反向索引中的JSON文档和字段。本节介绍NEST中可用的所有不同功能,使POCO和Elasticsearch变得轻而易举。

在Elasticsearch中显式映射文档对于为给定的问题域提供定制搜索解决方案至关重要。虽然Elasticsearch能够基于遇到的该类型的第一个文档来推断索引中给定类型的映射,但推测的映射有时不足以构建一个非常优秀的搜索体验。

要显式控制映射,可以在创建索引时指定显式类型映射,或者在索引该类型的第一个文档之前添加到现有索引(因为索引没有显式映射的文档将导致Elasticsearch推断映射)。

有几种方式来控制NEST中的映射

自动映射(从POCO属性类型推断) 属性映射 Fluent映射 通过访客模式

并且这些可以组合以形成整体映射方法。另外还有办法控制

忽略属性 多个字段

自动映射

在创建索引或通过Put Mapping API创建映射时,NEST提供了一个称为自动映射的功能,可以从您正在映射的CLR POCO属性类型自动推断出正确的Elasticsearch字段数据类型。

我们将通过一些示例来看自动映射的功能。 为此,我们将定义两个POCO Company,其有名称和Employees的集合以及具有不同类型的各种属性的Employee,并且本身具有Employee类型的集合。

public class Company { public string Name { get; set; } public List<Employee> Employees { get; set; } } public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int Salary { get; set; } public DateTime Birthday { get; set; } public bool IsManager { get; set; } public List<Employee> Employees { get; set; } public TimeSpan Hours { get; set; } }

自动映射可能不必为POCO上的所有属性定义手动映射

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Company>(m => m.AutoMap()) .Map<Employee>(m => m.AutoMap()) ); { "mappings": { "company": { "properties": { "employees": { "properties": { "birthday": { "type": "date" }, "employees": { "properties": {}, "type": "object" }, "firstName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "hours": { "type": "long" }, "isManager": { "type": "boolean" }, "lastName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "salary": { "type": "integer" } }, "type": "object" }, "name": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" } } }, "employee": { "properties": { "birthday": { "type": "date" }, "employees": { "properties": {}, "type": "object" }, "firstName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "hours": { "type": "long" }, "isManager": { "type": "boolean" }, "lastName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "salary": { "type": "integer" } } } } }

观察NEST基于我们的POCO属性的CLR类型推断了Elasticsearch类型。在这个例子中,

Birthday映射为`date`, Hours被映射为一个`long` `TimeSpan ticks`) IsManager映射为`boolean`, Salary被映射为`integer` Employees被映射为对象`object`

剩余的字符串属性作为多字段text数据类型,每个都有一个keyword数据类型子字段。

NEST已经推断出对以下.NET类型的映射支持

String映射到"text"与"keyword"子字段。请参阅[Multi Fields] Int32映射到"integer" UInt16映射到"integer" Int16映射到"short" Byte映射到"short" Int64映射到"long" UInt32映射到"long" TimeSpan映射到"long" Single映射到"float" Double映射到"double" Decimal映射到"double" UInt64映射到"double" DateTime映射到"date" DateTimeOffset映射到"date" Boolean映射到"boolean" Char映射到"keyword" Guid映射到"keyword"

并支持NEST中定义的一些特殊类型

Nest.GeoLocation映射到"geo_point" Nest.CompletionField映射到"completion" Nest.Attachment映射到"attachment" Nest.DateRange映射到"date_range" Nest.DoubleRange映射到"double_range" Nest.FloatRange映射到"float_range" Nest.IntegerRange映射到"integer_range" Nest.LongRange映射到"long_range"

所有其他类型默认映射到"object"。

某些.NET类型不具有直接等效的Elasticsearch类型。例如,System.Decimal是一种通常用于表示货币和其他财务计算的类型,需要大量有效的积分和分数位数,并且没有舍入误差。 Elasticsearch中没有等效的类型,最近的类型是double,双精度64位IEEE 754浮点数。

当POCO具有System.Decimal属性时,它将自动映射到Elasticsearch double类型。由于潜在的精度损失的警告,这在很多用例中通常是可以接受的,但是在某些边缘情况下可能会导致问题。

正如C#规范所述,

对于从十进制到float或double的转换,十进制值将舍入为最接近的double或float值。虽然这种转换可能会失去精度,但它并不会导致抛出异常。

C#规范部分6.2.1

此转换会导致在Decimal.MinValue和Decimal.MaxValue的反序列化时抛出异常,因为在序列化时,转换为最接近的双精度值分别在Decimal.MinValue或Decimal.MaxValue范围之外。在这些情况下,建议使用double作为POCO属性类型。


映射递归

如果您注意到我们以前的公司和员工示例,Employee类型是递归的,因为Employee类本身包含Employee类型的集合。 默认情况下,.AutoMap()只会在遇到像这样的递归实例时穿过一个深度; Employee类中的Employee类的集合没有映射其任何属性。

这是一个安全防范,以防止堆栈溢出和无限递归的函数。 此外,在大多数情况下,当涉及到Elasticsearch映射时,通常有一个这样的深度嵌套映射的边缘案例。 但是,您仍然可能需要执行此操作,因此您可以控制.AutoMap()的递归深度。

我们来介绍一个非常简单的类A,它本身有一个属性为A类的Child。

public class A { public A Child { get; set; } }

默认情况下,.AutoMap()只能进入深度1

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<A>(m => m.AutoMap()) );

因此,我们不会在第二次发生我们的Child属性时映射属性

{ "mappings": { "a": { "properties": { "child": { "properties": {}, "type": "object" } } } } }

现在我们来指定一个maxRecursion为3

var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<A>(m => m.AutoMap(3)) );

.AutoMap()现在已经映射了我们的Child属性的三个层级

{ "mappings": { "a": { "properties": { "child": { "type": "object", "properties": { "child": { "type": "object", "properties": { "child": { "type": "object", "properties": { "child": { "type": "object", "properties": {} } } } } } } } } } } }

属性映射

在自动映射中,您看到可以使用.AutoMap()从POCO的属性推断出POCO的类型映射。 但是,当您想要映射到推测的映射时,您会做什么? 这是属性映射可以帮助的地方。

可以使用您的POCO类型和属性上的属性来定义您的映射。 使用属性属性并调用.AutoMap(),NEST将从POCO属性类型推断映射,并考虑映射属性。

当您使用属性时,还必须调用.AutoMap()来获取要应用的属性。

这里我们定义与以前相同的两种类型,但这次使用属性来定义映射:

[ElasticsearchType(Name = "company")] public class Company { [Keyword(NullValue = "null", Similarity = "BM25")] public string Name { get; set; } [Text(Name = "office_hours")] public TimeSpan? HeadOfficeHours { get; set; } [Object(Store = false)] public List<Employee> Employees { get; set; } } [ElasticsearchType(Name = "employee")] public class Employee { [Text(Name = "first_name")] public string FirstName { get; set; } [Text(Name = "last_name")] public string LastName { get; set; } [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] public int Salary { get; set; } [Date(Format = "MMddyyyy")] public DateTime Birthday { get; set; } [Boolean(NullValue = false, Store = true)] public bool IsManager { get; set; } [Nested] [JsonProperty("empl")] public List<Employee> Employees { get; set; } }

然后我们通过调用.AutoMap()来映射类型

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Company>(m => m.AutoMap()) .Map<Employee>(m => m.AutoMap()) ); { "mappings": { "company": { "properties": { "employees": { "properties": { "birthday": { "format": "MMddyyyy", "type": "date" }, "empl": { "properties": {}, "type": "nested" }, "first_name": { "type": "text" }, "isManager": { "null_value": false, "store": true, "type": "boolean" }, "last_name": { "type": "text" }, "salary": { "coerce": true, "doc_values": false, "ignore_malformed": true, "type": "float" } }, "type": "object", "store": false }, "name": { "null_value": "null", "similarity": "BM25", "type": "keyword" }, "office_hours": { "type": "text" } } }, "employee": { "properties": { "birthday": { "format": "MMddyyyy", "type": "date" }, "empl": { "properties": {}, "type": "nested" }, "first_name": { "type": "text" }, "isManager": { "null_value": false, "store": true, "type": "boolean" }, "last_name": { "type": "text" }, "salary": { "coerce": true, "doc_values": false, "ignore_malformed": true, "type": "float" } } } } }

属性映射可以是一种方便的方法来控制如何使用最小的代码映射POCO,但是还有一些映射功能不能用属性表示,例如,多字段。 为了在NEST中拥有完整的映射功能,请查看Fluent Mapping。


Fluent映射

Fluent映射POCO属性到Elasticsearch类型映射中的字段提供了对该过程的最大控制。 通过Fluent映射,POCO的每个属性都被显式映射到一个Elasticsearch类型的字段映射。

为了演示,我们将定义两个POCO

Company拥有员工姓名和员工人数 Employee具有不同类型的各种属性,并且拥有Employee类型的集合。 public class Company { public string Name { get; set; } public List<Employee> Employees { get; set; } } public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int Salary { get; set; } public DateTime Birthday { get; set; } public bool IsManager { get; set; } public List<Employee> Employees { get; set; } public TimeSpan Hours { get; set; } }

手动映射

要为我们Company类型创建映射,我们可以使用Fluent的API并显式映射每个属性

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Company>(m => m .Properties(ps => ps .Text(s => s .Name(c => c.Name) ) .Object<Employee>(o => o .Name(c => c.Employees) .Properties(eps => eps .Text(s => s .Name(e => e.FirstName) ) .Text(s => s .Name(e => e.LastName) ) .Number(n => n .Name(e => e.Salary) .Type(NumberType.Integer) ) ) ) ) ) );

这里,Company类型的Name属性已被映射为文本数据类型,Employees属性映射为对象数据类型。 在此对象映射中,仅映射了Employee类型的FirstName,LastName和Salary属性。

这个例子的json映射看起来像

{ "mappings": { "company": { "properties": { "name": { "type": "text" }, "employees": { "type": "object", "properties": { "firstName": { "type": "text" }, "lastName": { "type": "text" }, "salary": { "type": "integer" } } } } } } }

以这种方式进行手动映射是强大的,但对于大型POCO可能会变得冗长乏味。 大多数时候,您只需要一次映射POCO的所有属性,而不必为每个属性指定映射,特别是当从CLR类型推导到映射到Elasticsearch类型时。

这是与自动映射相结合的Fluent映射.


自动映射与fluent 覆盖

在大多数情况下,您将需要映射不仅仅是vanilla数据类型,还可以为您的属性提供各种选项,例如要使用的分词器,是否启用doc_values等。

在这种情况下,可以将.AutoMap()与显式映射的属性结合使用。

在这里我们使用.AutoMap()自动推断Company类型从CLR属性类型的映射,但是我们覆盖Employees属性以使其成为一个嵌套的数据类型,因为默认情况下.AutoMap()将推断List<Employee>属性作为object数据类型

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Company>(m => m .AutoMap() .Properties(ps => ps .Nested<Employee>(n => n .Name(c => c.Employees) ) ) ) ); { "mappings": { "company": { "properties": { "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "employees": { "type": "nested" } } } } }

.AutoMap()是幂等的,因此在手动映射属性之前或之后调用它将仍然产生相同的结果。 下一个示例生成与上一个相同的映射

descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Company>(m => m .Properties(ps => ps .Nested<Employee>(n => n .Name(c => c.Employees) ) ) .AutoMap() ) );

正如我们能够在上一个例子中从自动映射重写推断的属性,fluent 映射也优先于属性映射。 这样可以组合 fluent ,属性和自动映射。 我们将以一个例子展示。 考虑以下两个POCOS

[ElasticsearchType(Name = "company")] public class CompanyWithAttributes { [Keyword(NullValue = "null", Similarity = "BM25")] public string Name { get; set; } [Text(Name = "office_hours")] public TimeSpan? HeadOfficeHours { get; set; } [Object(Store = false)] public List<Employee> Employees { get; set; } } [ElasticsearchType(Name = "employee")] public class EmployeeWithAttributes { [Text(Name = "first_name")] public string FirstName { get; set; } [Text(Name = "last_name")] public string LastName { get; set; } [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] public int Salary { get; set; } [Date(Format = "MMddyyyy")] public DateTime Birthday { get; set; } [Boolean(NullValue = false, Store = true)] public bool IsManager { get; set; } [Nested] [JsonProperty("empl")] public List<Employee> Employees { get; set; } }

现在,当映射时,调用AutoMap()来推断来自POCO属性类型和属性的映射,并且通过fluent映射来覆盖 推断的映射

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<CompanyWithAttributes>(m => m .AutoMap() .Properties(ps => ps .Nested<Employee>(n => n .Name(c => c.Employees) ) ) ) .Map<EmployeeWithAttributes>(m => m .AutoMap() .Properties(ps => ps .Text(s => s .Name(e => e.FirstName) .Fields(fs => fs .Keyword(ss => ss .Name("firstNameRaw") ) .TokenCount(t => t .Name("length") .Analyzer("standard") ) ) ) .Number(n => n .Name(e => e.Salary) .Type(NumberType.Double) .IgnoreMalformed(false) ) .Date(d => d .Name(e => e.Birthday) .Format("MM-dd-yy") ) ) ) ); { "mappings": { "company": { "properties": { "employees": { "type": "nested" }, "name": { "null_value": "null", "similarity": "BM25", "type": "keyword" }, "office_hours": { "type": "text" } } }, "employee": { "properties": { "birthday": { "format": "MM-dd-yy", "type": "date" }, "empl": { "properties": { "birthday": { "type": "date" }, "employees": { "properties": {}, "type": "object" }, "firstName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "hours": { "type": "long" }, "isManager": { "type": "boolean" }, "lastName": { "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "type": "text" }, "salary": { "type": "integer" } }, "type": "nested" }, "first_name": { "fields": { "firstNameRaw": { "type": "keyword" }, "length": { "analyzer": "standard", "type": "token_count" } }, "type": "text" }, "isManager": { "null_value": false, "store": true, "type": "boolean" }, "last_name": { "type": "text" }, "salary": { "ignore_malformed": false, "type": "double" } } } } }

自动映射将覆盖对象图

您可能已经在Automap with fluent overrides example中注意到例,表明Company上的Employees属性的属性未映射。 这是因为automapping 应用仅在Company映射的根级别。

通过在.Nested <Employee>映射中调用.AutoMap(),可以自动映射Employee嵌套属性,并再次通过手动映射来覆盖自动映射过程中的任何推断映射

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(m => m .Map<Company>(mm => mm .AutoMap() .Properties(p => p .Nested<Employee>(n => n .Name(c => c.Employees) .AutoMap() .Properties(pp => pp .Text(t => t .Name(e => e.FirstName) ) .Text(t => t .Name(e => e.LastName) ) .Nested<Employee>(nn => nn .Name(e => e.Employees) ) ) ) ) ) ); { "mappings": { "company": { "properties": { "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "employees": { "type": "nested", "properties": { "firstName": { "type": "text" }, "lastName": { "type": "text" }, "salary": { "type": "integer" }, "birthday": { "type": "date" }, "isManager": { "type": "boolean" }, "employees": { "type": "nested" }, "hours": { "type": "long" } } } } } } }

访客模式应用规范

还可以对所有或特定属性转换。

.AutoMap()在内部实现访问者模式。 默认访问者NoopPropertyVisitor不执行任何操作,也可以作为空白画布来实现您自己的访问方法。

例如,让我们创建一个自定义访问者,禁用数字和布尔类型的文档值(实际上不是一个好主意,但是为了清楚的例子,我们来做)。

public class Company { public string Name { get; set; } public List<Employee> Employees { get; set; } } public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int Salary { get; set; } public DateTime Birthday { get; set; } public bool IsManager { get; set; } public List<Employee> Employees { get; set; } public TimeSpan Hours { get; set; } }

我们先定义一个访客; 从NoopPropertyVisitor继承,并覆盖Visit方法以实现约定 是最简单的方法

public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor { public override void Visit( INumberProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { type.DocValues = false; } public override void Visit( IBooleanProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) { type.DocValues = false; } }

现在我们可以将自定义访问者的一个实例传递给.AutoMap()

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Employee>(m => m.AutoMap(new DisableDocValuesPropertyVisitor())) );

并且客户端任何时间将POCO(在本示例中为Employee)的属性映射为数字(INumberProperty)或布尔(IBooleanProperty),它将分别应用每个Visit()调用中定义的转换,在此示例中禁用doc_values。

var expected = new { mappings = new { employee = new { properties = new { birthday = new { type = "date" }, employees = new { properties = new { }, type = "object" }, firstName = new { type = "string" }, isManager = new { doc_values = false, type = "boolean" }, lastName = new { type = "string" }, salary = new { doc_values = false, type = "integer" } } } } };

访问PropertyInfo

您甚至可以进一步访问访问者,而不是访问IProperty类型,直接访问您的POCO反射的PropertyInfo属性。

例如,让我们创建一个访问者,将所有CLR类型映射到Elasticsearch文本数据类型ITextProperty)。

public class EverythingIsATextPropertyVisitor : NoopPropertyVisitor { public override IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => new TextProperty(); } var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Employee>(m => m.AutoMap(new EverythingIsATextPropertyVisitor())) ); { "mappings": { "employee": { "properties": { "birthday": { "type": "text" }, "employees": { "type": "text" }, "firstName": { "type": "text" }, "isManager": { "type": "text" }, "lastName": { "type": "text" }, "salary": { "type": "text" }, "hours": { "type": "text" } } } } }

忽略属性

POCO中的属性可以通过以下几种方式进行被忽映射:

对派生的ElasticsearchPropertyAttribute类型使用Ignore属性应用于在POCO上应该被忽略的属性 在ConnectionSettings上使用.InferMappingFor <TDocument>(Func <ClrTypeMappingDescriptor <TDocument>,IClrTypeMapping <TDocument >>选择器) 使用应用于使用的IElasticsearchSerializer所理解的POCO属性的ignore属性,并在序列化器上的CreatePropertyMapping()内进行检查。 在默认JsonNetSerializer的情况下,这是Json.NET JsonIgnoreAttribute

此示例演示了所有方法,使用属性上的Ignore属性忽略属性PropertyToIgnore,推断映射忽略属性AnotherPropertyToIgnore和json serializer特定属性以忽略属性JsonIgnoredProperty

[ElasticsearchType(Name = "company")] public class CompanyWithAttributesAndPropertiesToIgnore { public string Name { get; set; } [Text(Ignore = true)] public string PropertyToIgnore { get; set; } public string AnotherPropertyToIgnore { get; set; } [JsonIgnore] public string JsonIgnoredProperty { get; set; } }

除了Name 之外的所有属性都在映射中被忽略

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<CompanyWithAttributesAndPropertiesToIgnore>(m => m .AutoMap() ) ); var settings = WithConnectionSettings(s => s .InferMappingFor<CompanyWithAttributesAndPropertiesToIgnore>(i => i .Ignore(p => p.AnotherPropertyToIgnore) ) );

映射的JSON输出不包含被忽略的属性

{ "mappings": { "company": { "properties": { "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }

忽略继承的属性

通过在ConnectionSettings上使用POCO的推断映射配置,也可以忽略继承的属性。

public class Parent { public int Id { get; set; } public string Description { get; set; } public string IgnoreMe { get; set; } } public class Child : Parent { } var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Child>(m => m .AutoMap() ) ); var settings = WithConnectionSettings(s => s .InferMappingFor<Child>(m => m .Rename(p => p.Description, "desc") .Ignore(p => p.IgnoreMe) ) );

子映射的IgnoreMe属性被忽略

{ "mappings": { "child": { "properties": { "id": { "type": "integer" }, "desc": { "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } }, "type": "text" } } } } }

多个字段

通过不同的方式为Elasticsearch索引相同的字段,以达到不同的目的,例如将POCO string 属性映射为全文搜索的text数据类型,以及作为结构化搜索,排序和聚合的keyword数据类型的映射。 另一个例子是将POCO字符串属性映射为使用不同的分词器,以提供不同的全文搜索需求。

我们来看几个例子。 对于每个,我们使用以下简单的POCO

public class Person { public string Name { get; set; } }

String属性的默认映射

当使用自动映射时,字符串POCO类型的推断映射是具有多字段的text数据类型,包括keyword 子字段

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Person>(m => m .AutoMap() ) ); { "mappings": { "person": { "properties": { "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }

这是有用的,因为该属性可以用于全文搜索以及结构化搜索,排序和聚合

var searchResponse = client.Search<Person>(s => s .Query(q => q .Match(m => m .Field(f => f.Name) .Query("Russ") ) ) .Sort(ss => ss .Descending(f => f.Name.Suffix("keyword")) ) .Aggregations(a => a .Terms("peoples_names", t => t .Field(f => f.Name.Suffix("keyword")) ) ) ); { "query": { "match": { "name": { "query": "Russ" } } }, "sort": [ { "name.keyword": { "order": "desc" } } ], "aggs": { "peoples_names": { "terms": { "field": "name.keyword" } } } }

多个字段不会更改Elasticsearch中的原始_source字段; 它们仅影响一个字段的索引。

可以使用Put Mapping API将新的多个字段添加到现有的字段。

创建多个字段

可以使用字段映射中的.Fields()方法在映射上创建多个字段

var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map<Person>(m => m .Properties(p => p .Text(t => t .Name(n => n.Name) .Fields(ff => ff .Text(tt => tt .Name("stop") .Analyzer("stop") ) .Text(tt => tt .Name("shingles") .Analyzer("name_shingles") ) .Keyword(k => k .Name("keyword") .IgnoreAbove(256) ) ) ) ) ) ); { "mappings": { "person": { "properties": { "name": { "type": "text", "fields": { "stop": { "type": "text", "analyzer": "stop" }, "shingles": { "type": "text", "analyzer": "name_shingles" }, "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }
转载请注明原文地址: https://www.6miu.com/read-85662.html

最新回复(0)