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<Project>(2) { Document = project };
request = new IndexRequest<Project>(project) { };
当与完整的构造函数和手动通过文档进行比较时,DocumentPath
的优点变得明显。 将以下不使用DocumentPath
的请求与前面的示例进行比较
request = new IndexRequest<Project>(IndexName.From<Project>(), TypeName.From<Project>(), 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#表达式来强烈地键入这些字段路径字符串。
这些表达式分配给一个名为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 when
determining Field equality
getting the hash code for a Field instance
Important
Boost 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);
当指定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);
除了使用构造函数,您还可以将string
,PropertyInfo
和成员访问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);
为了简化从表达式创建一个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",
});