Elasticsearch.Nest 系列 4-1 映射:Auto mapping | 自动映射


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

一个简单示例:

  • 新建 Document 抽象基类。
  • 新建 Company 类
  • 新建 Employee 类
  • 三者关系如下

public abstract class Document
{
    public JoinField Join { get; set; }
}

public class Company : Document
{
    public string Name { get; set; }
    public List<Employee> Employees { get; set; }
}

public class Employee : Document
{
    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 类上的所有属性手动映射的麻烦。

以下示例将上面两个派生类索引到一个索引中:通过为基类调用 Map 方法,之后为每个派生类调用 AutoMap 方法:

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Document>(m => m
        .AutoMap<Company>()          //可以使用泛型方法进行自动映射
        .AutoMap(typeof(Employee))   //页可以使用非泛型方法进行自动映射,两者等效
    )
);

以上映射方法所产生的 JSON 结构如下:

{
  "mappings": {
    "properties": {
      "birthday": {
        "type": "date"
      },
      "employees": {
        "properties": {
          "birthday": {
            "type": "date"
          },
          "employees": {
            "properties": {},
            "type": "object"
          },
          "hours": {
            "type": "long"
          },
          "isManager": {
            "type": "boolean"
          },
          "join": {
            "properties": {},
            "type": "object"
          },
          "lastName": {
            "fields": {
              "keyword": {
                "ignore_above": 256,
                "type": "keyword"
              }
            },
            "type": "text"
          },
          "salary": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "hours": {
        "type": "long"
      },
      "isManager": {
        "type": "boolean"
      },
      "join": {
        "properties": {},
        "type": "object"
      },
      "lastName": {
        "fields": {
          "keyword": {
            "ignore_above": 256,
            "type": "keyword"
          }
        },
        "type": "text"
      },
      "name": {
        "fields": {
          "keyword": {
            "ignore_above": 256,
            "type": "keyword"
          }
        },
        "type": "text"
      },
      "salary": {
        "type": "integer"
      }
    }
  }
}

以上 JSON 中,NEST 根据 POCOs 属性的数据类型,推断了对应的 ES 数据类型:

  • Birthday:date
  • Hours:long
  • IsManager:boolean
  • Salary:integer
  • Employees:object
  • 其余的 string 类型被推断为了 ES 中的 text 类型,且每一个都被标记为了 keyword。

目前 NEST 提供的类型推断映射

支持的 .NET 数据类型

.NET 数据类型 ES 中的数据类型
String text,包含 keyword 子属性,更多关于多字段说明,见此博文 NEST 教程系列 4-7 映射: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
  • 需要注意:.NET 中的 Decimal 类型在 ES 中没有直接等效的类型,这里用 double 来对应,但存在精度上存在差异(Decimal 精度 小于 Double 精度),在反序列化到 .NET POCO 模型的时候,如果 ES 中的值在 [Decimal.Min, Decimal.Max] 区间范围之外,则在映射到 POCO 模型 Decimal 类型的属性时由于超精度了,就会引发异常。为了避免这种情况,在设计 .NET 模型属性的时候,建议不要用 Decimal 数据类型,转而使用 Double。

NEST 特有的特殊类型映射

NEST 数据类型 ES 中的数据类型
Nest.QueryContainer percolator
Nest.GeoLocation geo_point
Nest.IGeoShape geo_shape,如果你想要显式映射 shape 类型,则在对应的属性上使用 [Shape]特性
Nest.CompletionField completion
Nest.DateRange date_range
Nest.DoubleRange double_range
Nest.FloatRange float_range
Nest.IntegerRange integer_range
Nest.LongRange long_range
Nest.IpAddressRange ip_range

除去以上两大类,剩余其他类型默认都被映射为 object。

映射递归

在上面的例子中,Employee 模型内嵌了一个 List 模型,默认情况下,使用 .AutoMap() 进行自动映射的,在遍历实例的时候只会遍历一层深度,也就是 List 属性并没有得到任何属性的映射。

这样设计的目的时为了防止堆栈溢出和无限递归。另外,在大多数情况下,当涉及到 Elasticsearch 映射时,具有这种深层嵌套的映射通常是一个边界情况。但是,会有这样的场景存在,因此你可以通过 .AutoMap(int maxRecursion) 重载方法来指定递归的深度。

示例:

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

直接使用 AutoMap


var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<A>(m => m.AutoMap()) 
);

结果如下:

{
  "myindex" : {
    "mappings" : {
      "properties" : {
        "child" : {
          "type" : "object"
        }
      }
    }
  }
}

显式指定 AutoMap 的递归深度为 3:

createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<A>(m => m.AutoMap(3)) //深度 3
);

结果如下:

{
  "myindex" : {
    "mappings" : {
      "properties" : {
        "child" : {
          "properties" : {
            "child" : {
              "properties" : {
                "child" : {
                  "properties" : {
                    "child" : {
                      "type" : "object"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

常言道:学然后知不足,教然后知困。

我知道你的焦虑,一起共进加油:P

关不关注都无所谓,会根据生活节奏紧凑度定期分享些开发经验、搬砖生涯、痛点、感悟。

欢迎关注我的订阅号:P