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

字段名称与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",
});

你可能感兴趣的:(.net平台下分布式开发技术)