链式映射(Fluent mapping)

921 发布于: 2021-04-07 读完约需 4 分钟

概述

Fluent直译为“流畅的”,在NESTFluent mapping其实是不断地,链式地调用静态扩展方法,所以,笔者将其译为“链式映射”

链式映射让.NET开发者可以最大程度地控制C#实体到Elasticsearch文档、字段等的数据类型映射关系。

为了演示,以下定义两个实体类:

  • Company类,其中包含一个字符串类型的属性成员Name和一个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; }
}

使用链式映射配置映射关系

以下使用链式映射来配置实体的映射关系:

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .Properties(ps => ps
            .Text(s => s
                .Name(n => n.Name)
            )
            .Object<Employee>(o => o
                .Name(n => n.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属性被映射成了text类型,而Employees属性被映射成了object类型。在Employee类中,仅有FirstName,LastName,Salary3个属性定义了映射关系,运行后得到的Elasticsearch映射结果为:

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

以这种方式手动映射数据类型的功能虽然强大,但对于大型实体来说,可能会变得冗长和繁琐。大多数情况下,你只是想为实体的单个属性成员配置映射关系,而不必为每个属性指定映射,特别是当存在从CLR类型到Elasticsearch类型的推断映射时。

这时,我们就需要使用链式映射和自动映射相结合了。

自动映射与链式映射相结合

在大多数情况下,我们需要映射的不仅仅是普通的数据类型,还需要为属性提供各种选项,比如指定要使用的分词器、是否启用doc_values等。

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

以下示例中,使用.AutoMap()从CLR属性类型自动推断出Company类型的映射,但是我们接着使用.Nested()方法重写了Employees属性使其成为嵌套数据类型,因为默认情况下.AutoMap()将推断List<Employee>属性为对象数据类型,如下:

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .AutoMap()
        .Properties(ps => ps
            .Nested<Employee>(n => n
                .Name(nn => nn.Employees)
            )
        )
    )
);

运行得到的Elasticsearch映射JSON结果为:

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

由于.AutoMap()方法是幂等的,因此在链式映射属性之前或之后调用它结果是一样的。下面这个示例生成的映射与前一个相同:

createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .Properties(ps => ps
            .Nested<Employee>(n => n
                .Name(nn => nn.Employees)
            )
        )
        .AutoMap()
    )
);

自动映射重写特性映射

在前面的示例中,我们学习了使用自动映射来重写NEST推断类型,同样的链式映射也优先于特性映射。通过这种方式,可以将链式、特性和自动映射结合起来。下面我们将用一个例子来演示。

定义需要用到的实体类,如下:

[ElasticsearchType(RelationName = "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<EmployeeWithAttributes> Employees { get; set; }
}

[ElasticsearchType(RelationName = "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]
    [PropertyName("empl")]
    public List<Employee> Employees { get; set; }
}

现在,将链式、特性和自动映射结合起来:

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<CompanyWithAttributes>(m => m
        .AutoMap() // 自动映射Company
        .Properties(ps => ps // 重写Company中的推断映射
            .Nested<EmployeeWithAttributes>(n => n
                .Name(nn => nn.Employees)
                .AutoMap() // 自动映射内嵌的Employee类型
                .Properties(pps => pps // 重视Employee的推断映射
                    .Text(s => s
                        .Name(e => e.FirstName)
                        .Fields(fs => fs
                            .Keyword(ss => ss
                                .Name("firstNameRaw")
                            )
                            .TokenCount(t => t
                                .Name("length")
                                .Analyzer("standard")
                            )
                        )
                    )
                    .Number(nu => nu
                        .Name(e => e.Salary)
                        .Type(NumberType.Double)
                        .IgnoreMalformed(false)
                    )
                    .Date(d => d
                        .Name(e => e.Birthday)
                        .Format("MM-dd-yy")
                    )
                )
            )
        )
    )
);

运行后得到的Elasticsearch映射关系JSON结果为:

{
  "mappings": {
    "properties": {
      "employees": {
        "type": "nested",
        "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"
          }
        }
      },
      "name": {
        "null_value": "null",
        "similarity": "BM25",
        "type": "keyword"
      },
      "office_hours": {
        "type": "text"
      }
    }
  }
}

运行时字段映射

运行时字段是在查询时计算的字段。运行时字段可以在索引的映射中定义。

在以下示例中,我们定义了CompanyRuntimeFields类,它包含一个BirthDayOfWeek的属性成员,然后我们可以在强类型运行时字段映射中使用该属性,示例代码如下:

public class CompanyRuntimeFields
{
    public string BirthDayOfWeek { get; set; }
}

var createIndexResponse = _client.Indices.Create("myindex", c => c
        .Map<Company>(m => m
            .RuntimeFields<CompanyRuntimeFields>(rtf => rtf // 使用`CompanyRuntimeFields`作为泛型参数
                .RuntimeField(f => f.BirthDayOfWeek, FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"))) // 使用`BirthDayOfWeek`作为运行时字段名
        )
    );

运行后得到的Elasticsearch映射结果为:

{
  "mappings": {
    "runtime": {
      "birthDayOfWeek": {
        "type": "keyword",
        "script": {
          "lang": "painless",
          "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
        }
      }
    }
  }
}

没有必要为运行时字段映射定义类型。运行时字段可以通过提供字符串名称来定义,如下:

createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .RuntimeFields(rtf => rtf
            .RuntimeField("birthDayOfWeek", FieldType.Keyword, f => f.Script("emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))")))
    )
);

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册