不少 Elasticsearch API 接口在使用的时候会希望知道字段在原始文档中的路径(以字符串的形式),NEST 提供了 Field 类来允许你获得这些字段路径字符串。
构造函数
通过使用 Field 的构造函数:
1 2 3 4 5 6 7 8 var fieldString = new Field("name" );var fieldProperty = new Field(typeof (Project).GetProperty(nameof (Project.Name)));Expression<Func<Project, object >> expression = p => p.Name; var fieldExpression = new Field(expression);
你也可以在实例化的时候设定 boost 值。
eg:当你使用如下构造函数时:
1 public Field (string name, double ? boost = null , string format = null )
1 2 3 4 5 var fieldString = "name^2.1" ; var field = new Field(fieldString);var field = new Field("name" , 2.1 );
隐式转换
跟构造函数一样,你也可以直接将 string,PropertyInfo,lambda 表达式隐式转换为 Field 类,如下:
1 2 3 4 5 6 Field fieldString = "name" ; Field fieldProperty = typeof (Project).GetProperty(nameof (Project.Name)); Expression<Func<Project, object >> expression = p => p.Name; Field fieldExpression = expression;
使用 Nest.Infer 方法
通过 Nest.Infer 可以简化从表达式创建 Field 实例:
1 2 3 4 5 var field = Nest.Infer.Field<Project>(p => p.Name);var field = Nest.Infer.Field<Project>(p => p.Name, 2.1 );
Field 名称的自动大小写转换
默认情况下,NEST 为了跟 JavaScript 和 JSON 统一,对 Field 使用 camelCase。
通过使用 ConnectionSettings 的 DefaultFieldNameInferrer() 方法,你可以修改默认行为,如下:
1 2 var settings = new ConnectionSettings(new Uri(_esSettings.ServerUri)) .DefaultFieldNameInferrer(dfni=>dfni.ToUpper())
但是,当使用下面的构造函数的时候,总是逐字匹配的(不会转换,传入什么样就是什么样):
1 public Field (string name, double ? boost = null , string format = null )
如果您希望NEST完全不更改字段名称的大小写,只需将Func<string,string>传递给 DefaultFieldNameInferrer 即可,该函数仅返回输入字符串:
1 2 var settings = new ConnectionSettings(new Uri(_esSettings.ServerUri)) .DefaultFieldNameInferrer(p => p)
使用表达式
需要注意的是:表达式只支持成员变量。
支持多级嵌套属性:
1 Expect("leadDeveloper.firstName" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.LeadDeveloper.FirstName));
处理集合索引器时,将忽略索引器访问权限,你可以遍历集合的属性:
1 Expect("curatedTags" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.CuratedTags[0 ]));
支持 Linq:
1 2 3 Expect("curatedTags" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.CuratedTags.First())); Expect("curatedTags.added" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.CuratedTags[0 ].Added)); Expect("curatedTags.name" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.CuratedTags.First().Name));
字典:
1 2 3 4 5 6 Expect("metadata.hardcoded" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.Metadata["hardcoded" ])); Expect("metadata.hardcoded.created" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.Metadata["hardcoded" ].Created)); var variable = "var" ;Expect("metadata.var" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.Metadata[variable])); Expect("metadata.var.created" ).WhenSerializing(Nest.Infer.Field<Project>(p => p.Metadata[variable].Created));
如果使用的是 Elasticearch 的多字段,这些“虚拟”子字段并不总是映射回你的 POCO 对象。通过在表达式上调用 .Suffix(),可以描述应映射的子字段及其映射方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Expect("leadDeveloper.firstName.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.LeadDeveloper.FirstName.Suffix("raw" ))); Expect("curatedTags.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.CuratedTags[0 ].Suffix("raw" ))); Expect("curatedTags.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.CuratedTags.First().Suffix("raw" ))); Expect("curatedTags.added.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.CuratedTags[0 ].Added.Suffix("raw" ))); Expect("metadata.hardcoded.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.Metadata["hardcoded" ].Suffix("raw" ))); Expect("metadata.hardcoded.created.raw" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.Metadata["hardcoded" ].Created.Suffix("raw" )));
你也可以直接链式使用 Suffix 方法
1 2 Expect("curatedTags.name.raw.evendeeper" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.CuratedTags.First().Name.Suffix("raw" ).Suffix("evendeeper" )));
传递给 Suffix 方法的值也将会被正确估算:
1 2 3 4 5 6 7 var variable = "var" ;var suffix = "unanalyzed" ;Expect("metadata.var.unanalyzed" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.Metadata[variable].Suffix(suffix))); Expect("metadata.var.created.unanalyzed" ).WhenSerializing( Nest.Infer.Field<Project>(p => p.Metadata[variable].Created.Suffix(suffix)));
也可以使用 .AppendSuffix() 方法将后缀“追加”到表达式中。在要将相同的后缀应用于字段列表的情况下,这很有用:
1 2 3 4 5 6 7 8 var expressions = new List<Expression<Func<Project, object >>>{ p => p.Name, p => p.Description, p => p.CuratedTags.First().Name, p => p.LeadDeveloper.FirstName, p => p.Metadata["hardcoded" ] };
在每个后面增加“raw后缀”:
1 2 3 4 5 6 7 8 var fieldExpressions = expressions.Select<Expression<Func<Project, object >>, Field>(e => e.AppendSuffix("raw" )).ToList(); Expect("name.raw" ).WhenSerializing(fieldExpressions[0 ]); Expect("description.raw" ).WhenSerializing(fieldExpressions[1 ]); Expect("curatedTags.name.raw" ).WhenSerializing(fieldExpressions[2 ]); Expect("leadDeveloper.firstName.raw" ).WhenSerializing(fieldExpressions[3 ]); Expect("metadata.hardcoded.raw" ).WhenSerializing(fieldExpressions[4 ]);
你也可以链式调用 .AppendSuffix()
1 2 3 4 5 6 7 8 var multiSuffixFieldExpressions = expressions.Select<Expression<Func<Project, object >>, Field>(e => e.AppendSuffix("raw" ).AppendSuffix("evendeeper" )).ToList(); Expect("name.raw.evendeeper" ).WhenSerializing(multiSuffixFieldExpressions[0 ]); Expect("description.raw.evendeeper" ).WhenSerializing(multiSuffixFieldExpressions[1 ]); Expect("curatedTags.name.raw.evendeeper" ).WhenSerializing(multiSuffixFieldExpressions[2 ]); Expect("leadDeveloper.firstName.raw.evendeeper" ).WhenSerializing(multiSuffixFieldExpressions[3 ]); Expect("metadata.hardcoded.raw.evendeeper" ).WhenSerializing(multiSuffixFieldExpressions[4 ]);
使用特性
使用 NEST 的属性特性可以让你指定一个新的名称来代替属性名。
使用 Text 特性:
1 2 3 4 5 6 7 public class BuiltIn { [Text(Name = "naam" ) ] public string Name { get ; set ; } } Expect("naam" ).WhenSerializing(Nest.Infer.Field<BuiltIn>(p => p.Name));
使用 DataMember 特性:
1 2 3 4 5 6 7 public class DataMember { [DataMember(Name = "nameFromDataMember" ) ] public string Name { get ; set ; } } Expect("nameFromDataMember" ).WhenSerializing(Nest.Infer.Field<DataMember>(p => p.Name));
使用各序列化器的特性:
1 2 3 4 5 6 7 public class SerializerSpecific { [PropertyName("nameInJson" ), JsonProperty("nameInJson" ) ] public string Name { get ; set ; } } Expect("nameInJson" ).WhenSerializing(Nest.Infer.Field<SerializerSpecific>(p => p.Name));
如果在一个属性上同时指定了 Nest 提供的属性特性和序列化器提供的属性特性,Nest 特性优先级较高,如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class Both { [Text(Name = "naam" ) ] [PropertyName("nameInJson" ), DataMember(Name = "nameInJson" ) ] public string Name { get ; set ; } } Expect("naam" ).WhenSerializing(Nest.Infer.Field<Both>(p => p.Name)); Expect(new { naam = "Martijn Laarman" }).WhenSerializing(new Both { Name = "Martijn Laarman" });
字段推断缓存
每个 ConnectionSettings 实例都会缓存字段名称的解析:
假设有如下演示类:
1 2 3 4 5 6 7 8 class A { public C C { get ; set ; } }class B { public C C { get ; set ; } }class C { public string Name { get ; set ; } }
对于如下表达式代码:
1 2 var fieldNameOnA = _client.Infer.Field(Nest.Infer.Field<A>(p => p.C.Name));var fieldNameOnB = _client.Infer.Field(Nest.Infer.Field<B>(p => p.C.Name));
有:
现在创建一个新的 ConnectionSettings,重新映射 A.C 为 “d”:
1 2 3 4 5 6 7 8 9 10 11 12 var newConnectionSettings = new TestConnectionSettings() .DefaultMappingFor<A>(m => m .PropertyName(p => p.C, "d" ) ); var newClient = new ElasticClient(newConnectionSettings);fieldNameOnA = newClient.Infer.Field(Nest.Infer.Field<A>(p => p.C.Name)); fieldNameOnB = newClient.Infer.Field(Nest.Infer.Field<B>(p => p.C.Name)); fieldNameOnA.Should().Be("d.name" ); fieldNameOnB.Should().Be("c.name" );
推断字段名称的优先顺序
如上述,设定字段名称的方式如下:
在 ConnectionSettings上使用 .PropertyName 命名属性。
使用 Nest 的 PropertyNameAttribute。
使用序列化器提供的特性。
使用 DataMemberAttribute。
在 ConnectionSettings 上使用 DefaultFieldNameInferrer,默认情况下将使用骆驼命名属性。
假设有以下测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private class Precedence { [Text(Name = "renamedIgnoresNest" ) ] [PropertyName("renamedIgnoresJsonProperty" ),JsonProperty("renamedIgnoresJsonProperty" )] public string RenamedOnConnectionSettings { get ; set ; } [Text(Name = "nestAtt" ) ] [PropertyName("nestProp" ),JsonProperty("jsonProp" ) ] public string NestAttribute { get ; set ; } [PropertyName("nestProp" ),JsonProperty("jsonProp" ) ] public string NestProperty { get ; set ; } [DataMember(Name ="jsonProp" ) ] public string JsonProperty { get ; set ; } [DataMember(Name = "data" ) ] public string DataMember { get ; set ; } }
优先级如下:
即使此属性应用了各种属性,如果在 ConnectionSettings 上进行了设定,则 ConnectionSettings 上设置的优先级最高。
同时具有TextAttribute,PropertyNameAttribute 和 JsonPropertyAttribute:TextAttribute 优先级高。
同时具有 PropertyNameAttribute 和 JsonPropertyAttribute:PropertyNameAttribute 优先级高。
JsonPropertyAttribute 优先级高。
DataMemberAttribute 优先级高。
自定义一个 IPropertyMappingProvider:将所有 AskSerializer 命名为 ask,并在 ConnectionSettings 中进行注册
1 2 3 4 5 6 7 8 9 private class CustomPropertyMappingProvider : PropertyMappingProvider { public override IPropertyMapping CreatePropertyMapping (MemberInfo memberInfo ) { return memberInfo.Name == nameof (Precedence.AskSerializer) ? new PropertyMapping { Name = "ask" } : base .CreatePropertyMapping(memberInfo); } }
在 ConnectionSettings 中注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var usingSettings = WithConnectionSettings(s => s .DefaultMappingFor<Precedence>(m => m .PropertyName(p => p.RenamedOnConnectionSettings, "renamed" ) ) .DefaultFieldNameInferrer(p => p.ToUpperInvariant()) ).WithPropertyMappingProvider(new CustomPropertyMappingProvider()); usingSettings.Expect("renamed" ).ForField(Nest.Infer.Field<Precedence>(p => p.RenamedOnConnectionSettings)); usingSettings.Expect("nestAtt" ).ForField(Nest.Infer.Field<Precedence>(p => p.NestAttribute)); usingSettings.Expect("nestProp" ).ForField(Nest.Infer.Field<Precedence>(p => p.NestProperty)); usingSettings.Expect("jsonProp" ).ForField(Nest.Infer.Field<Precedence>(p => p.JsonProperty)); usingSettings.Expect("ask" ).ForField(Nest.Infer.Field<Precedence>(p => p.AskSerializer)); usingSettings.Expect("data" ).ForField(Nest.Infer.Field<Precedence>(p => p.DataMember)); usingSettings.Expect("DEFAULTFIELDNAMEINFERRER" ).ForField(Nest.Infer.Field<Precedence>(p => p.DefaultFieldNameInferrer));
索引文档时也适用相同的命名规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 usingSettings.Expect(new [] { "ask" , "DEFAULTFIELDNAMEINFERRER" , "jsonProp" , "nestProp" , "nestAtt" , "renamed" , "data" }).AsPropertiesOf(new Precedence { RenamedOnConnectionSettings = "renamed on connection settings" , NestAttribute = "using a nest attribute" , NestProperty = "using a nest property" , JsonProperty = "the default serializer resolves json property attributes" , AskSerializer = "serializer fiddled with this one" , DefaultFieldNameInferrer = "shouting much?" , DataMember = "using a DataMember attribute" });
覆盖继承的字段推断
从基本类型继承的属性可以忽略,并在 ConnectionSetting 上使用 DefaultMappingFor。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class Parent { public int Id { get ; set ; } public string Description { get ; set ; } public string IgnoreMe { get ; set ; } } public class Child : Parent { }var usingSettings = WithConnectionSettings(s => s .DefaultMappingFor<Child>(m => m .PropertyName(p => p.Description, "desc" ) .Ignore(p => p.IgnoreMe) ) ); usingSettings.Expect(new [] { "id" , "desc" , }).AsPropertiesOf(new Child { Id = 1 , Description = "this property will be renamed for Child" , IgnoreMe = "this property will be ignored (won't be serialized) for Child" , }); public class SourceModel { [PropertyName("gexo" ) ] public GeoModel Geo { get ; set ; } } public class GeoModel { [DataMember(Name = "country_iso_code" ) ] public string CountryIsoCode { get ; set ; } } var usingSettings = WithConnectionSettings(s => s) .WithSourceSerializer(JsonNetSerializer.Default); usingSettings.Expect("gexo" ).ForField(Field<SourceModel>(p=>p.Geo)); usingSettings.Expect("country_iso_code" ).ForField(Field<GeoModel>(p=>p.CountryIsoCode)); usingSettings.Expect(new [] { "country_iso_code" , }).AsPropertiesOf(new GeoModel { CountryIsoCode = "nl" }); usingSettings.Expect(new [] { "gexo" , }).AsPropertiesOf(new SourceModel { Geo = new GeoModel { CountryIsoCode = "nl" } }); usingSettings.Expect("gexo.country_iso_code" ).ForField(Field<SourceModel>(p=>p.Geo.CountryIsoCode));