开始使用NEST

1193 更新于: 2021-03-26 读完约需 7 分钟

概述

虽然NEST是一个高阶的Elasticsearch.Net客户端,但它仍然与原生的Elasticsearch API有着非常紧密的映射关系。所有请求和响应都通过类型进行公开,这使得它非常适合快速使用和运行。

在底层,NEST使用Elasticsearch.Net这个低阶客户端来分发请求和响应,并扩展了Elasticsearch.Net中的许多类型。在NEST中,.NET开发者仍然可以通过.LowLevel属性来访问Elasticsearch.Net低阶客户端的接口和方法。

使用NEST连接到Elasticsearch服务

如果要使用NEST连接到本地默认端口的Elasticsearch服务http://localhost:9200,你只需要实例化一个ElasticClient,如下:

var client = new ElasticClient();

但通常情况下,如果Elasticsearch运行在远程服务器,你可能需要向客户端传递额外的配置选项,比如Elasticsearch的地址。这时可以使用ConnectionConfiguration,创建一个ConnectionConfiguration实例,并为客户端设置合适的配置参数即可,如下:

var settings = new ConnectionSettings(new Uri("http://example.com:9200"))
    .DefaultIndex("people");

var client = new ElasticClient(settings);

在本例中,使用DefaultIndex()方法来指定了一个默认索引,如果之后的请求中未指定索引名称,那么请求将路由到这个默认设置的索引。ConnectionSettings上还有许多其他配置选项,它们自ConnectionConfigurationConnectionConfiguration类型用于在Elasticsearch.Net中将额外的配置选项传递给底层客户端。

指定一个默认索引是可选的,但如果一个请求中没有任何索引名称,那么NEST会抛出异常。要了解有关如何为请求指定索引的更多信息,请参见索引名推断

ConnectionSettings并没有被限制为Elasticsearch传递单个地址。NEST提供了几种不同类型的连接池,每种连接池具有不同的特征,可用于配置不同的客户端。

下面的示例使用了一个SniffingConnectionPool连接池,它包含集群中三个Elasticsearch节点的地址,客户端将使用这种类型的池来维护集群中可用节点的列表,它可以以轮询的方式向这些节点发送请求。

var uris = new[]
{
    new Uri("http://localhost:9200"),
    new Uri("http://localhost:9201"),
    new Uri("http://localhost:9202"),
};

var connectionPool = new SniffingConnectionPool(uris);
var settings = new ConnectionSettings(connectionPool)
    .DefaultIndex("people");

var client = new ElasticClient(settings);

索引(Indexing)

NEST客户端配置好并连接到Elasticsearch远程服务后,我们需要将一些数据写入集群中进行测试。

假如有如下的C#实体模型(Plain Old CLR Object (POCO)):

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

创建单个实例对象的索引文档同时支持同步和异步操作,如下:

var person = new Person
{
    Id = 1,
    FirstName = "Martijn",
    LastName = "Laarman"
};
var client = CodeDefaultElasticClientFactory.GetClient;
var indexResponse = client.IndexDocument(person); // 同步方法创建索引文档,返回IndexResponse对象
Console.WriteLine($"创建索引文档的响应结果为:{indexResponse}");

//var asyncIndexResponse = await client.IndexDocumentAsync(person); // 异步方法创建索引文档,返回Task<IndexResponse>

NEST中所有可用方法均同时对外公开为同步和异步两种版本,后者使用惯用的*Async后缀作为异步方法名。

运行结果为:

创建索引文档的响应结果为:Valid NEST response built from a successful (201) low level call on PUT: /people/_doc/1

以上示例将在/people/_doc/1端点创建索引文档。NEST可以根据POCO实体对象中的Id属性来自动推断文档的id,更多的Id相关配置请参考这里

默认情况下,当将POCO序列化为JSON文档并发送给Elasticsearch时,NEST 会使用驼峰法则来序列化POCO上的属性名。你可以在ConnectionSettings上使用.DefaultFieldNameInferrer(Func<string,string>)方法来调整这种默认的属性名称序列化规则。

以上写入文档操作会自动在Elasticsearch服务中创建名为people的索引,映射关系也是自动推断出来的,如下:

{
    "people": {
        "aliases": {},
        "mappings": {
            "properties": {
                "firstName": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type": "keyword",
                            "ignore_above": 256
                        }
                    }
                },
                "id": {
                    "type": "long"
                },
                "lastName": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type": "keyword",
                            "ignore_above": 256
                        }
                    }
                }
            }
        },
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "people",
                "creation_date": "1616726484804",
                "number_of_replicas": "1",
                "uuid": "SBwUkpBbRqSxQK9U9FZwlw",
                "version": {
                    "created": "7120099"
                }
            }
        }
    }
}

批量写入索引文档(Bulk Indexing)

NEST同样提供了批量创建索引文档的功能,通过调用NEST实例的Bulk()方法,如下:

var people = new List<Person>
{
    new Person {Id = 1, FirstName = "Martijn", LastName = "Laarman"},
    new Person {Id = 2, FirstName = "Greg", LastName = "Marzouka"},
    new Person {Id = 3, FirstName = "Russ", LastName = "Cam"}
};
var client = CodeDefaultElasticClientFactory.GetClient;
var indexResponse = client.Bulk(b=>b
                                .Index("people")
                                .IndexMany(people)
                               );
Console.WriteLine($"批量创建索引文档的响应结果为:{indexResponse}");

运行结果为:

批量创建索引文档的响应结果为:Valid NEST response built from a successful (200) low level call on POST: /people/_bulk

搜索索引文档(Searching)

上面我们已经为一些文档建立了索引,现在可以开始搜索它们了。

public static void Search()
{
    var client = CodeDefaultElasticClientFactory.GetClient;
    var searchResponse = client.Search<Person>(s => s
        .From(0)
        .Size(10)
        .Query(q => q
            .Match(m => m
                .Field(f => f.FirstName)
                .Query("Martijn")
            )
        )
    );
    var people = searchResponse.Documents;
    Console.WriteLine($"搜索到{searchResponse.Total}条记录");
    foreach (var person in people)
    {
        Console.WriteLine($"id: {person.Id}, first name:{person.FirstName}, last name:{person.LastName}");
    }
}

运行结果为:

搜索到1条记录
id: 1, first name:Martijn, last name:Laarman

上以搜索示例代码将从索引中取前10条满足FirstNameMartijn的文档,搜索的Elasticsearch路径为/people/_search,索引名称为people,这是之前在ConnectionSettings中配置的默认索引名称。

同样的,你可以使用.AllIndices()方法在当前连接的Elasticsearch服务的所有文档中进行搜索,如下:

var searchResponse = client.Search<Person>(s => s
    .AllIndices()
    .From(0)
    .Size(10)
    .Query(q => q
         .Match(m => m
            .Field(f => f.FirstName)
            .Query("Martijn")
         )
    )
);

它会生成一个端点为/_search的Elasticsearch搜索请求。

你可以在请求中提供单个或多个索引名,请分别参考有关索引路径文档路径的文档。

到目前为止,所有的搜索示例都使用了NEST的Fluent API,该API使用lambda表达式构造一个查询,其结构模仿了Elasticsearch基于JSON的查询DSL中的查询结构。

对于那些不喜欢深度嵌套lambda表达式的人来说,NEST还提供了一个对象初始化器语法,也可以用于构造查询。

下面是与前一个示例相同的查询,这次使用对象初始化器语法构造:

var searchRequest = new SearchRequest<Person>(Nest.Indices.All) 
{
    From = 0,
    Size = 10,
    Query = new MatchQuery
    {
        Field = Infer.Field<Person>(f => f.FirstName),
        Query = "Martijn"
    }
};

var searchResponse = await client.SearchAsync<Person>(searchRequest);

如本节开始所示,高阶客户端NEST提供了访问Elasticsearch.Net低阶客户端的方法,即通过访问NEST实例的. low - level属性,如下:

var searchResponse = client.LowLevel.Search<SearchResponse<Person>>("people", PostData.Serializable(new
{
    from = 0,
    size = 10,
    query = new
    {
        match = new
        {
            field = "firstName",
            query = "Martijn"
        }
    }
}));

var responseJson = searchResponse;

这里的查询是匿名类型,但响应的主体是从高阶客户端NEST返回的与响应类型相同的具体实现。

聚合统计(Aggregations)

除了结构化和非结构化搜索之外,Elasticsearch还能够进行基于搜索查询的聚合统计:

public static void Aggregation()
{
    var client = CodeDefaultElasticClientFactory.GetClient;
    var searchResponse = client.Search<Person>(s => s
        .Size(0)
        .Query(q => q
            .Match(m => m
                .Field(f => f.FirstName)
                .Query("Martijn")
            )
        )
        .Aggregations(a => a
            .Terms("last_names", ta => ta
                //.Field(f=>f.LastName) //由于自动创建的索引的lastName字段的类型类型为text,但不能直接使用text数据类型进行聚合,所以这里不能直接使用表达式来设置Field,而是要用下面的方式
                .Field("lastName.keyword")
            )
        )
    );
    var termsAggregation = searchResponse.Aggregations.Terms("last_names");
    Console.WriteLine($"聚合结果:{searchResponse.Aggregations.Count}");
    foreach (var bucket in termsAggregation.Buckets)
    {
        Console.WriteLine($"{bucket.Key}, {bucket.DocCount.ToString()}");
    }
}

运行后的聚合统计结果为:

聚合结果:1
Laarman, 1

在上面的聚合示例代码中,match查询用来指定字段firstName为值Martijn的搜索条件,但此处的Size(0)被设置成了0,因为我们不期望返回被搜索到的索引文档内容而只需要返回聚合统计后的结果。

在聚合中使用Terms来指定要分组的字段。我们可以从termsAggregation这个变量中获取到每个按lastName分组的聚合分桶的文档计数。

关于详细的聚合统计将在后面专门的章节介绍。

版权声明:本作品系原创,版权归码友网所有,如未经许可,禁止任何形式转载,违者必究。

发表评论

登录用户才能发表评论, 请 登 录 或者 注册