概述
Fluent
直译为“流畅的”,在NEST
中Fluent 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
,Salary
3个属性定义了映射关系,运行后得到的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))")))
)
);
发表评论
登录用户才能发表评论, 请 登 录 或者 注册