Elasticsearch(十二)【NEST高级客户端--规范】

规范 NEST有一些规范用于推理

文档路径 API功能 字段 IDS 索引名称 索引路径 属性

文档路径 Elasticsearch中的许多API描述了一个文档的路径。 在NEST中,除了生成一个构造函数之外,还分别创建了Index,Type和Id,我们还生成一个构造函数,可以使用DocumentPath类型的实例更简洁地描述文档的路径。
创建新实例 这里我们创建一个基于项目的id为1的新文档路径
IDocumentPath path = new DocumentPath(1); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect(1).WhenSerializing(path.Id);

您仍然可以覆盖推断索引和类型名称
path = new DocumentPath(1).Type("project1"); Expect("project1").WhenSerializing(path.Type); path = new DocumentPath(1).Index("project1"); Expect("project1").WhenSerializing(path.Index);

并且还有一种静态方式来描述这样的路径
path = DocumentPath.Id(1); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect(1).WhenSerializing(path.Id);

从文档类型实例创建 如果您有一个文档的实例,您也可以使用它来生成文档路径
var project = new Project { Name = "hello-world" };

这里我们根据Project,project的实例创建一个新的文档路径
IDocumentPath path = new DocumentPath(project); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect("hello-world").WhenSerializing(path.Id);

您仍然可以覆盖推断索引和类型名称
path = new DocumentPath(project).Type("project1"); Expect("project1").WhenSerializing(path.Type); path = new DocumentPath(project).Index("project1"); Expect("project1").WhenSerializing(path.Index);

而且,还有一种描述这种路径的静态方式
path = DocumentPath.Id(project); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect("hello-world").WhenSerializing(path.Id); DocumentPath p = project;

具有请求的示例
var project = new Project { Name = "hello-world" };

我们可以看到DocumentPath如何帮助您更简单地描述您的请求的示例
var request = new IndexRequest(2) { Document = project }; request = new IndexRequest(project) { };

当与完整的构造函数和手动通过文档进行比较时,DocumentPath的优点变得明显。 将以下不使用DocumentPath的请求与前面的示例进行比较
request = new IndexRequest(IndexName.From(), TypeName.From(), 2) { Document = project };

特征推论 Elasticsearch中的一些URI采用了功能枚举。 在NEST中,URI上的路由值表示为实现接口IUrlParameter的类。 由于枚举不能在C#中实现接口,所以将使用Feature枚举隐式转换为的Features类来表示Feature类型的路由参数。
构造函数 直接使用Features构造函数是可行的,而是涉及到
Features fieldString = Feature.Mappings | Feature.Aliases; Expect("_mappings,_aliases") .WhenSerializing(fieldString);

这里我们新建一个GET索引elasticsearch 请求,它需要Indices和Features。 注意我们如何直接使用Feature枚举。
var request = new GetIndexRequest(All, Feature.Settings);

字段推测 Elasticsearch API中的几个地方期望从原始源文档中的字段的路径作为字符串值。 NEST允许您使用C#表达式来强烈地键入这些字段路径字符串。
【Elasticsearch(十二)【NEST高级客户端--规范】】这些表达式分配给一个名为Field的类型,并且有几种方法来创建一个实例
构造函数 直接使用构造函数是可行的,但是当从成员访问lambda表达式解析时可以相当的参与
var fieldString = new Field("name"); var fieldProperty = new Field(typeof(Project).GetProperty(nameof(Project.Name))); Expression expression = p => p.Name; var fieldExpression = new Field(expression); Expect("name") .WhenSerializing(fieldExpression) .WhenSerializing(fieldString) .WhenSerializing(fieldProperty); When using the constructor and passing a value for Name, Property or Expression, ComparisonValue is also set on the Field instance; this is used whendetermining Field equality getting the hash code for a Field instance ImportantBoost values are not taken into account when determining equality.var fieldStringWithBoostTwo = new Field("name^2"); var fieldStringWithBoostThree = new Field("name^3"); Expression expression = p => p.Name; var fieldExpression = new Field(expression); var fieldProperty = new Field(typeof(Project).GetProperty(nameof(Project.Name))); fieldStringWithBoostTwo.GetHashCode().Should().NotBe(0); fieldStringWithBoostThree.GetHashCode().Should().NotBe(0); fieldExpression.GetHashCode().Should().NotBe(0); fieldProperty.GetHashCode().Should().NotBe(0); fieldStringWithBoostTwo.Should().Be(fieldStringWithBoostThree);

字段名称与Boost 当指定Field名称时,该名称可以包括boost值; NEST将拆分名称和提升值并设置Boost属性; 作为字符串一部分的boost值优先于可以作为第二个构造函数参数传递的boost值
Field fieldString = "name^2"; Field fieldStringConstructor = new Field("name^2"); Field fieldStringCreate = new Field("name^2", 3); fieldString.Name.Should().Be("name"); fieldStringConstructor.Name.Should().Be("name"); fieldStringCreate.Name.Should().Be("name"); fieldString.Boost.Should().Be(2); fieldStringConstructor.Boost.Should().Be(2); fieldStringCreate.Boost.Should().Be(2);

隐性转换 除了使用构造函数,您还可以将stringPropertyInfo和成员访问lambda表达式隐式转换为Field。 然而,对于表达式,这仍然是相当重要的,因为表达式首先需要分配给明确指定表达式委托类型的变量。
Field fieldString = "name"; Field fieldProperty = typeof(Project).GetProperty(nameof(Project.Name)); Expression expression = p => p.Name; Field fieldExpression = expression; Expect("name") .WhenSerializing(fieldString) .WhenSerializing(fieldProperty) .WhenSerializing(fieldExpression);

使用Nest.Infer方法 为了简化从表达式创建一个Field实例,可以使用一个静态Infer类
此示例使用静态导入using static Nest.Infer; 在使用指令中将Nest.Infer.Field()简化为Field()。 如果复制任何这些示例,请确保包含此静态导入
Field fieldString = "name";

但是对于表达式来说,这仍然是相当涉及的
var fieldExpression = Infer.Field(p => p.Name);

这甚至可以使用静态导入进一步缩短。 现在我们第一个使用构造函数的例子更简单了!
fieldExpression = Field(p => p.Name); Expect("name") .WhenSerializing(fieldString) .WhenSerializing(fieldExpression);

您可以使用字符串以及使用Nest.Infer.Field在字段中指定boosts
fieldString = "name^2.1"; fieldString.Boost.Should().Be(2.1); fieldExpression = Field(p => p.Name, 2.1); fieldExpression.Boost.Should().Be(2.1); Expect("name^2.1") .WhenSerializing(fieldString) .WhenSerializing(fieldExpression);

字段命名风格 默认情况下,NEST可以使所有字段名称更好地与典型的JavaScript和JSON约定相一致
ConnectionSettings上使用DefaultFieldNameInferrer(),可以更改此行为
var setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p.ToUpper())); setup.Expect("NAME").WhenSerializing(Field(p => p.Name));

然而,string类型始终是逐字逐行传递的
setup.Expect("NaMe").WhenSerializing("NaMe");

您希望表达式具有相同的行为,只需将Func传递给DefaultFieldNameInferrer即可更改名称
setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p)); setup.Expect("Name").WhenSerializing(Field(p => p.Name));

复杂字段名称表达式 您可以按照您的属性表达任何深度。 这里我们正在遍历LeadDeveloper FirstName
Expect("leadDeveloper.firstName").WhenSerializing(Field(p => p.LeadDeveloper.FirstName));

处理集合索引器时,索引器访问被忽略,允许您遍历集合的属性
Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags[0]));

同样,LINQ的.First()方法也可以
Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags.First())); Expect("curatedTags.added").WhenSerializing(Field(p => p.CuratedTags[0].Added)); Expect("curatedTags.name").WhenSerializing(Field(p => p.CuratedTags.First().Name));

记住,这些是表达式,而不是将被执行的实际代码
假设字典上的索引器描述属性名称
Expect("metadata.hardcoded").WhenSerializing(Field(p => p.Metadata["hardcoded"])); Expect("metadata.hardcoded.created").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created));

这里的一个很酷的功能是NEST将评估传递给索引器的变量
var variable = "var"; Expect("metadata.var").WhenSerializing(Field(p => p.Metadata[variable])); Expect("metadata.var.created").WhenSerializing(Field(p => p.Metadata[variable].Created));

如果您使用Elasticearch的多字段,那么您真的应该使用它们,因为它们允许您以多种不同的方式分析字符串,这些”virtual”子字段并不总是映射到您的POCO。 通过在表达式上调用.Suffix(),可以描述应该映射的子字段以及它们的映射方式
Expect("leadDeveloper.firstName.raw").WhenSerializing( Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); Expect("curatedTags.raw").WhenSerializing( Field(p => p.CuratedTags[0].Suffix("raw"))); Expect("curatedTags.raw").WhenSerializing( Field(p => p.CuratedTags.First().Suffix("raw"))); Expect("curatedTags.added.raw").WhenSerializing( Field(p => p.CuratedTags[0].Added.Suffix("raw"))); Expect("metadata.hardcoded.raw").WhenSerializing( Field(p => p.Metadata["hardcoded"].Suffix("raw"))); Expect("metadata.hardcoded.created.raw").WhenSerializing( Field(p => p.Metadata["hardcoded"].Created.Suffix("raw")));

你甚至可以链接.Suffix()调用任何深度!
Expect("curatedTags.name.raw.evendeeper").WhenSerializing( Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper")));

传递给后缀的变量也将被评估
var suffix = "unanalyzed"; Expect("metadata.var.unanalyzed").WhenSerializing( Field(p => p.Metadata[variable].Suffix(suffix))); Expect("metadata.var.created.unanalyzed").WhenSerializing( Field(p => p.Metadata[variable].Created.Suffix(suffix)));

后缀也可以使用.AppendSuffix()附加到表达式。 在您要将相同后缀应用于字段列表的情况下,这是非常有用的。
这里我们有一个表达式列表
var expressions = new List> { p => p.Name, p => p.Description, p => p.CuratedTags.First().Name, p => p.LeadDeveloper.FirstName, p => p.Metadata["hardcoded"] };

并且我们要为每个添加后缀“raw”
var fieldExpressions = expressions.Select, 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()调用
var multiSuffixFieldExpressions = expressions.Select, 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的属性特性可以为属性指定一个新名称
public class BuiltIn { [Text(Name = "naam")] public string Name { get; set; } }Expect("naam").WhenSerializing(Field(p => p.Name));

从NEST 2.x开始,我们还要求序列化程序是否可以将属性解析为名称。 在这里,我们要求默认的JsonNetSerializer解析一个属性名称,并将JsonPropertyAttribute考虑在内
public class SerializerSpecific { [JsonProperty("nameInJson")] public string Name { get; set; } }Expect("nameInJson").WhenSerializing(Field(p => p.Name));

如果属性中都存在NEST属性特性和序列化器特定属性,则NEST属性优先
public class Both { [Text(Name = "naam")] [JsonProperty("nameInJson")] public string Name { get; set; } }Expect("naam").WhenSerializing(Field(p => p.Name)); Expect(new { naam = "Martijn Laarman" }).WhenSerializing(new Both { Name = "Martijn Laarman" });

字段推测缓存 每个连接设置实例缓存字段名称的解析。 为了演示,请采取以下简单的POCO
class A { public C C { get; set; } }class B { public C C { get; set; } }class C { public string Name { get; set; } }var client = TestClient.Default; var fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); var fieldNameOnB = client.Infer.Field(Field(p => p.C.Name));

这里我们有两个类似形状的表达式,一个来自A,一个来自B,将按照预期解析为相同的字段名称
fieldNameOnA.Should().Be("c.name"); fieldNameOnB.Should().Be("c.name");

现在,当我们在A上解析属性C的字段路径时,现在我们创建一个新的连接设置,使用A类的C映射到"d",这与B上的属性C不同
var newConnectionSettings = TestClient.CreateSettings(modifySettings: s => s .InferMappingFor(m => m .Rename(p => p.C, "d") ) ); var newClient = new ElasticClient(newConnectionSettings); fieldNameOnA = newClient.Infer.Field(Field(p => p.C.Name)); fieldNameOnB = newClient.Infer.Field(Field(p => p.C.Name)); fieldNameOnA.Should().Be("d.name"); fieldNameOnB.Should().Be("c.name");

然而,我们并没有使用其单独的连接设置来破坏第一个客户端实例的推断
fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); fieldNameOnA.Should().Be("c.name"); fieldNameOnB.Should().Be("c.name");

推理优先 要包装,推断字段名称的优先级为:
使用`.Rename()`连接设置上的属性的硬编码重命名 NEST属性映射 询问序列化器是否具有逐字数值,例如它具有显式的`JsonProperty`属性。 将MemberInfo的名称传递给`DefaultFieldNameInferrer`,默认情况下为camelCases

以下示例类将演示此优先级
class Precedence { [Text(Name = "renamedIgnoresNest")] [JsonProperty("renamedIgnoresJsonProperty")] public string RenamedOnConnectionSettings { get; set; } [Text(Name = "nestAtt")] [JsonProperty("jsonProp")] public string NestAttribute { get; set; } [JsonProperty("jsonProp")] public string JsonProperty { get; set; } [JsonProperty("dontaskme")] public string AskSerializer { get; set; } public string DefaultFieldNameInferrer { get; set; } }

在这里,我们创建一个自定义序列化器,重命名任何名为AskSerializer的属性为ask
class CustomSerializer : JsonNetSerializer { public CustomSerializer(IConnectionSettingsValues settings) : base(settings) { }public override IPropertyMapping CreatePropertyMapping(MemberInfo memberInfo) { return memberInfo.Name == nameof(Precedence.AskSerializer) ? new PropertyMapping { Name = "ask" } : base.CreatePropertyMapping(memberInfo); } }

在这里,我们使用.Rename()ConnectionSettings上的属性进行显式重命名,并且不会逐字地映射的所有属性应该是大写
var usingSettings = WithConnectionSettings(s => s.InferMappingFor
(m => m .Rename(p => p.RenamedOnConnectionSettings, "renamed") ) .DefaultFieldNameInferrer(p => p.ToUpperInvariant()) ).WithSerializer(s => new CustomSerializer(s)); usingSettings.Expect("renamed").ForField(Field
(p => p.RenamedOnConnectionSettings)); usingSettings.Expect("nestAtt").ForField(Field
(p => p.NestAttribute)); usingSettings.Expect("jsonProp").ForField(Field
(p => p.JsonProperty)); usingSettings.Expect("ask").ForField(Field
(p => p.AskSerializer)); usingSettings.Expect("DEFAULTFIELDNAMEINFERRER").ForField(Field
(p => p.DefaultFieldNameInferrer));

索引文档时也适用相同的命名规则
usingSettings.Expect(new [] { "ask", "DEFAULTFIELDNAMEINFERRER", "jsonProp", "nestAtt", "renamed" }).AsPropertiesOf(new Precedence { RenamedOnConnectionSettings = "renamed on connection settings", NestAttribute = "using a nest attribute", JsonProperty = "the default serializer resolves json property attributes", AskSerializer = "serializer fiddled with this one", DefaultFieldNameInferrer = "shouting much?" }); 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 .InferMappingFor(m => m .Rename(p => p.Description, "desc") .Ignore(p => p.IgnoreMe) ) ); usingSettings.Expect(new [] { "id", "desc", }).AsPropertiesOf(new Child { Id = 1, Description = "using a nest attribute", IgnoreMe = "the default serializer resolves json property attributes", });

    推荐阅读