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

手动映射

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .Properties(ps => ps
            .Text(s => s                       //映射为 text 数据类型
                .Name(n => n.Name) 
            )
            .Object<Employee>(o => o  //属性映射为 Object
                .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 数据类型
  • Company.Employees 属性被映射为 object 类型,另外该属性对应的仅对 Employee 类中的 FirstName,LastName 以及 Salary 这 3 个属性进行了映射。

最终 json 如下:

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

手动映射的方式自定义程度强大,但如何 POCO 类很复杂的话,一个一个手动指定就让人崩溃了。

在日常开发中,我们一般只是需要对部分属性进行显式指定,跟 automapper 一样,NEST 同样提供了部分显式映射的功能:使用 .AutoMap() 将 CLR 属性类型先自动映射一波,之后通过显式指定(覆盖的方式)进行自定义调整。

  • 默认情况下使用 .AutoMap()将 List属性作为 object 数据类型,这里我们通过 Nested 方法进行显式覆盖指定,示例如下:

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<Company>(m => m
        .AutoMap()                               //1
        .Properties(ps => ps
            .Nested<Employee>(n => n     //2
                .Name(nn => nn.Employees)
            )
        )
        //.AutoMap()      在这里执行跟放 "1" 处执行,等效。
    )
);
  • .AutoMap() 是一个幂函数,调用的顺序不影响最终的结果,即上面的代码,如果把 AutoMap 方法放到 Properties 方法之后,效果是一样的。

映射结果如下:

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

Fluent mapping 的优先级别 Auto mapping 的优先级要高,同时比 Attribute Mapping 的优先级也要高,基于此,你也可以把 Fluent mapping 和 Attribute mapping 结合使用,如下:

假设有如下 POCO 类

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

结合 Auto mapping 和 Attributes mapping

var createIndexResponse = _client.Indices.Create("myindex", c => c
    .Map<CompanyWithAttributes>(m => m
        .AutoMap()                                              // 1.自动映射 company
        .Properties(ps => ps                                  // 2.覆盖利用特性指定的值
            .Nested<EmployeeWithAttributes>(n => n
                .Name(nn => nn.Employees)
                .AutoMap()                                      // 3.自动映射嵌套的 employee
                .Properties(pps => pps                      // 4.覆盖利用特性指定的值
                    .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")
                    )
                )
            )
        )
    )
);

结果如下:

{
  "myindex" : {
    "mappings" : {
      "properties" : {
        "employees" : {
          "type" : "nested",
          "properties" : {
            "birthday" : {
              "type" : "date",
              "format" : "MM-dd-yy"
            },
            "empl" : {
              "type" : "nested",
              "properties" : {
                "birthday" : {
                  "type" : "date"
                },
                "employees" : {
                  "type" : "object"
                },
                "firstName" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                },
                "hours" : {
                  "type" : "long"
                },
                "isManager" : {
                  "type" : "boolean"
                },
                "lastName" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                },
                "salary" : {
                  "type" : "integer"
                }
              }
            },
            "first_name" : {
              "type" : "text",
              "fields" : {
                "firstNameRaw" : {
                  "type" : "keyword"
                },
                "length" : {
                  "type" : "token_count",
                  "analyzer" : "standard"
                }
              }
            },
            "isManager" : {
              "type" : "boolean",
              "store" : true,
              "null_value" : false
            },
            "last_name" : {
              "type" : "text"
            },
            "salary" : {
              "type" : "double",
              "ignore_malformed" : false
            }
          }
        },
        "name" : {
          "type" : "keyword",
          "similarity" : "BM25",
          "null_value" : "null"
        },
        "office_hours" : {
          "type" : "text"
        }
      }
    }
  }
}
  • 通过在 .Nested 内部调用 .AutoMap() 方法,可以自动映射 Employee 的嵌套属性,并再次通过手动映射来覆盖自动推断的映射。