标签:Elasticsearch.Nest系列

Elasticsearch.Nest 教程系列 9-8 转换:Routing Inference | 路由推断


有的时候,我们在索引文档的时候,会要求指定 routing,否则会报“[routing] is missing for ….”之类的错误,ES 原生直接通过 ?routing 的形式来进行指定,如:

PUT /index/_doc/1?routing=1  
{
    "parentProperty": "a parent prop",
    "id": 1,
    "parentChildRelation": {
        "name":"myparent"
    }
}

那么在 NEST 中,如何指定 routing?NEST 提供了以下几种方法:

隐式转换

以下数据类型会自动进行隐式转换来显式创建路由:

  • Int32
  • Int64
  • String
  • Guid

以上几个数据类型,在被声明为 Routing 类型的 方法/属性使用的时候,会自动进行隐式转换——转换成 Routing 类型,如下:

Elasticsearch.Nest 教程系列 4-5 映射:Parent/Child relationships | 子父级关系映射


如果把 官方示例 放在你本地进行测试,会发现没有问题,但当你连接测试 ES 服务器的时候,就会报各种错误:

  • 这是因为官方使用了 InMemoryConnection ,所有请求并不会真实发送到测试 ES 服务器,规避掉了不少现实问题。以下示例基于官方文档进行了修正,使用本地搭建测试 ES 服务器。

父子关系的数据结构,在日常开发过程中,使用还是比较平凡的:

  • 在 ES 6.x 之前,你可以在一个索引中包含多种 type(类型)。通过给定类型的特殊 _parent 字段映射,可以创建 1对N 关系 的父-子文档。
    • 通过这种方式在索引子项时,你需要传递一个 _parent id 作为路由键(需要确保父项、其子项及所有祖先都保存在同一个分片上)。
  • 但从 ES 6.x 开始,type 不再支持多类型,固定 _type=_doc。(另外在 ES5 的时候,ES 的 index 相当于 DB,_type 相当于 DB 中的表名,但从 ES7 开始,ES 的 index 相当于 DB中的表,_type 固定为 _doc)

嵌套对象和父子结构文档的差异对比和使用建议:

Nested Object Parent/Child
优点 文档存储再一起,读取性能高 父子文档可以独立更新
缺点 更新嵌套的子文档时,需要更新整个文档 需要额外的内存维护关系。读取性能相对差
适用场景 子文档偶尔更新,以查询为主 子文档更新频繁

既然索引不再允许将不同 _type 类型存储在同一索引中,那么如何创建父联接?

Elasticsearch.Nest 教程系列 4-4 映射:through the Visitor Pattern | 通过访问者模式进行映射


通过访问者模式,可以对所有或者特定的属性进行应用转换。

.AutoMap() 方法内部实现了 Visitor Pattern,默认访问者 NoopPropertyVisitor 不执行任何操作,并允许你实现自己的访问方法。

常规使用:

示例:自定义一个访问者,以禁用数字和布尔类型的 doc 值:

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; }
}

Elasticsearch.Nest 教程系列 4-3 映射:Fluent mapping | 通过 lambda 表达式流畅映射


Fluent mapping 允许你可以最大程度地控制 POCOs 到 ES 字段的映射。你可以认为这种方式类似于 Automapper 中通过 CreateMap<T1, T2>().ForMember 来显式指定属性之间的映射。Fluent mapping 同样需要你显式指定每一个 POCO 属性跟 ES 字段的映射关系。

下面使用 Company 和 Employee 这 2 个 类来进行演示,两者之间的关系如下:

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; }
}

Elasticsearch.Nest 教程系列 4-2 映射:Attribute mapping | 通过特性进行映射


使用自动映射的时候,NEST 会将 POCO 模型属性的数据类型自动推断出对应的 ES 数据类型。同样,NEST 也提供了一系列特性,方便你进行一些定制。

唯一需要注意的是,虽然使用特性进行了映射,但依然需要调用 .AutoMap() 方法,以便在特性上自定义的值生效。

示例:

在 Employee 和 Skill 类上使用特性

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


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

一个简单示例:

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

Elasticsearch.Nest 教程系列 4 映射


虽然 ES 可以自动推断部分类型(如 string, bool, number)并进行映射,但无法满足在所有业务场景,显式指定模型映射至关重要。

本章节主要说明如何通过 NEST 将 .NET 中 POCOs 模型的属性跟 ES 中存储的 JSON 文档和字段进行映射。

NEST 提供了如下几种转换方式:

以上几种映射方式可以组合起来以形成整体映射方法。

此外,还有一些控制方法:

Elasticsearch.Nest 教程系列 3-2 序列化:Extending NEST types | 扩展 NEST 类型

NEST 的默认 JSON 序列化知道如何正确的序列化所有请求和响应类型,以及如何正确处理的你的 .NET 模型类。然而有的时候,你可能需要修改默认行为或者自定义你自己的序列化器,本章节将说明如何自定义以及扩展 NEST 类型。


有时为了解决某些问题,或者因为你使用了扩展了 Elasticsearch 功能的第三方插件,需要你提供某一种类型的自定义实现,而 NEST 并没有提供现成的支持。这个时候就可以通过如下方式来扩展 NEST 的类型使得在某些场景支持:

创建你自己的属性映射

通过实现自定义的 IProperty 实现,将 POCO 属性跟 NEST 字段进行映射。

public class MyPluginProperty : IProperty
{
    IDictionary<string, object> IProperty.LocalMetadata { get; set; }
    public string Type { get; set; } = "my_plugin_property";
    public PropertyName Name { get; set; }

    public MyPluginProperty(string name, string language)
    {
        this.Name = name;
        this.Language = language;
        this.Numeric = true;
    }

    [PropertyName("language")]
    public string Language { get; set; }

    [PropertyName("numeric")]
    public bool Numeric { get; set; }
}
  • PropertyNameAttribute 特性用于标记应序列化的属性。如果没有此属性,NEST将不会选择该属性进行序列化。

在创建索引的时候使用自定义的 IProperty 实现

Elasticsearch.Nest 教程系列 3-1 序列化:Custom Serialization | 自定义序列化

NEST 的默认 JSON 序列化知道如何正确的序列化所有请求和响应类型,以及如何正确处理的你的 .NET 模型类。然而有的时候,你可能需要修改默认行为或者自定义你自己的序列化器,本章节将说明如何自定义以及扩展 NEST 类型。


目前 NEST 客户端已经完全移除了 SimpleJson 和 Newtonsoft.Json,转而使用内置的 Utf8Json—一种直接与 UTF-8 二进制文件直接协同工作的快速序列化器。

随着转而使用 UtfJson,NEST 团队在 7.x 阶段删除了部分原先在之前 NEST 客户端中存在的 JSON 特性,变动如下:

  • 由于性能原因,由 Utf8Json 生成的 JSON 不会缩进。如请求中的 JSON 不会缩进,哪怕指定了 SerializationFormatting.Indented。但 NEST 团队正在考虑公开缩进 JSON 的选项以便于开发和调试。
  • NEST 类型不能通过继承扩展。在 6.x 版本,可以通过派生该类型并注释这些新属性来为该类型包括其他属性。在当前使用 Utf8Json 进行序列化的时候,此方法将不起作用。
  • 序列化器使用 Reflection.Emit,而 Utf8Json 使用 Reflection.Emit 来生成高效的格式化器。但并非所有平台都支持 Reflection.Emit,例如 UWP,Xamarin.iOS 和 Xamarin.Android。
  • Elasticsearch.Net.DynamicResponse 将 JSON 数组反序列化为 List。SimpleJson 将 JSON 数组反序列化为 object[]。但出于分配和性能方面的原因,Utf8Json 将它们反序列化为 List
  • 将 JSON 对象字段名称反序列化为 C# 类的属性时,Utf8Json 更加严格。在 6.x 版本中,内部使用 Json.NET 进行序列化,JSON 对象字段名称会先跟 C# 的类属性名完全匹配的进行匹配,之后再使用不区分大小写的进行匹配。但在 7.x 版本中(使用 Utf8Json 后),名称必须完全匹配。

注入新的序列化器

通过注入新的序列化器,可以让你在_source,_fields 或希望写入和返回用户提供的值的任何地方对(反)序列化进行调用。

Elasticsearch.Nest 教程系列 2-4 连接:Working with certificates | 使用凭证连接


使用证书

如果你已经通过 Elastic Stack Security 功能在 Elasticsearch 上启用了SSL,或者在 ES 服务上层的代理上启用了SSL,并且生成证书的证书颁发机构(CA)受运行客户端代码的计算机信任,那么你不需要进行任何操作。

如果你使用不受信任的 CA 证书(如你自己生成的CA 证书),默认情况下, .NET 不允许你使用 https 请求终结点,不过你可以使用 ServicePointManager.ServerCertificateValidationCallback 来解决这个问题。大部分的示例可能会让你直接返回一个 true,但并不建议你这样做,因为这么设置会允许所有到你应用程序域的 HTTPS 的流量,且无需验证:

//不建议这么干:
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, errors) => true
  • 这么设置不会对 HTTPS 连接请求jinxing yanzheng

配置验证

可以使用 Elasticsearch.Net 的 ConnectionConfiguration 和使用 NEST 的ConnectionSettings 公开该回调。你也可以在这处理程序中进行自己的验证,也可以使用静态类 CertificateValidations 提供的一些处理器。