ElasticSearch Nest 插入/更新 [英] ElasticSearch Nest Insert/Update

查看:103
本文介绍了ElasticSearch Nest 插入/更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用以下查询在弹性中创建了一个索引:

I have created an index in elastic using the following query:

PUT public_site
{
  "mappings": {
    "page": {
      "properties": {
        "url": {
          "type": "string"
        },
        "title":{
          "type": "string"
        },
        "body":{
          "type": "string"
        },
        "meta_description":{
          "type": "string"
        },
        "keywords":{
          "type": "string"
        },
        "category":{
          "type": "string"
        },
        "last_updated_date":{
          "type": "date"
        },
        "source_id":{
        "type":"string"
        }
      }
    }
  }
}

我想使用 .net NEST 库将文档插入到此索引中.我的问题是 .net 更新方法的签名对我没有任何意义.

I would like to insert a document into this index using the .net NEST library. My issue is that the .net update method's signature doesn't make any sense to me.

client.Update<TDocument>(IUpdateRequest<TDocument,TPartialDocument>)

Java 库对我来说更有意义:

The Java library makes so much more sense to me:

UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index("index");
updateRequest.type("type");
updateRequest.id("1");
updateRequest.doc(jsonBuilder()
        .startObject()
            .field("gender", "male")
        .endObject());
client.update(updateRequest).get();

在 NEST 中,TDocumentTPartialDocument 类从何而来?我制作的这些 C# 类是代表我的索引吗?

In NEST where do the TDocument and TPartialDocument classes come from? Are these C# classes that I make representing my index?

推荐答案

TDocumentTPartialDocument

  • 在 Elasticsearch (TDocument) 和
  • 中表示一个文档
  • 在执行部分更新时,Elasticsearch 中部分文档的表示 (TPartialDocument).
  • represent a document in Elasticsearch (TDocument) and
  • a representation of part of the the document in Elasticsearch (TPartialDocument), when performing a partial update.

在完全更新的情况下,TDocumentTPartialDocument 可能指代相同的具体 POCO 类型.让我们看一些例子来演示.

In the case of a full update, TDocument and TPartialDocument may refer to the same concrete POCO type. Let's have a look at some examples to demonstrate.

让我们使用您在上面定义的映射创建一个索引.首先,我们可以使用 POCO 类型来表示文档

Let's create an index with the mapping that you have defined above. Firstly, we can represent a document using a POCO type

public class Page
{
    public string Url { get; set; }

    public string Title { get; set; }

    public string Body { get; set; }

    [String(Name="meta_description")]
    public string MetaDescription { get; set; }

    public IList<string> Keywords { get; set; }

    public string Category { get; set; }

    [Date(Name="last_updated_date")]
    public DateTimeOffset LastUpdatedDate { get; set; }

    [String(Name="source_id")]
    public string SourceId { get; set; }
}

默认情况下,当 NEST 序列化 POCO 属性时,它使用驼峰命名约定.因为您的索引具有某些属性的蛇形外壳,例如"last_updated_date",我们可以覆盖 NEST 使用属性将这些序列化的名称.

By default, when NEST serializes POCO properties it uses camel casing naming convention. Because your index has snake casing for some properties e.g. "last_updated_date", we can override the name that NEST serializes these to using attributes.

接下来,让我们创建要使用的客户端

Next, let's create the client to work with

var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var pagesIndex = "pages";
var connectionSettings = new ConnectionSettings(pool)
        .DefaultIndex(pagesIndex)
        .PrettyJson()
        .DisableDirectStreaming()
        .OnRequestCompleted(response =>
            {
                // log out the request
                if (response.RequestBodyInBytes != null)
                {
                    Console.WriteLine(
                        $"{response.HttpMethod} {response.Uri} 
" +
                        $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}");
                }
                else
                {
                    Console.WriteLine($"{response.HttpMethod} {response.Uri}");
                }

                Console.WriteLine();

                // log out the response
                if (response.ResponseBodyInBytes != null)
                {
                    Console.WriteLine($"Status: {response.HttpStatusCode}
" +
                             $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}
" +
                             $"{new string('-', 30)}
");
                }
                else
                {
                    Console.WriteLine($"Status: {response.HttpStatusCode}
" +
                             $"{new string('-', 30)}
");
                }
            });

var client = new ElasticClient(connectionSettings);

连接设置的配置方式有助于开发;

Connection settings has been configured in a way that is helpful whilst developing;

  1. DefaultIndex() - 默认索引已配置为 "pages".如果没有在请求中传递明确的索引名称,并且无法为 POCO 推断出索引名称,则将使用默认索引.
  2. PrettyJson() - 美化(即缩进)json 请求和响应.这对于查看向 Elasticsearch 发送和接收的内容非常有用.
  3. DisableDirectStreaming() - 默认情况下,NEST 将 POCO 序列化到请求流并从响应流反序列化响应类型.禁用此直接流将缓冲内存流中的请求和响应字节,允许我们在 OnRequestCompleted()
  4. 中将它们注销
  5. OnRequestCompleted() - 在收到响应后调用.这使我们可以在开发过程中注销请求和响应.
  1. DefaultIndex() - The default index has been configured to be "pages". If no explicit index name is passed on a request and no index name can be inferred for a POCO, then the default index will be used.
  2. PrettyJson() - Prettify (i.e. indent) json requests and responses. This will be useful to see what is being sent to and received from Elasticsearch.
  3. DisableDirectStreaming() - NEST by default serializes POCOs to the request stream and deserializes response types from the response stream. Disabling this direct streaming will buffer the request and response bytes in memory streams, allowing us to log them out in OnRequestCompleted()
  4. OnRequestCompleted() - Called after a response is received. This allows us to log out requests and responses whilst we're developing.

2、3 和 4 在开发过程中很有用,但会带来一些性能开销,因此您可能决定不在生产中使用它们.

2, 3 and 4 are useful during development but will come with some performance overhead so you may decide not to use them in production.

现在,让我们用页面映射创建索引

Now, let's create the index with the Page mapping

// delete the index if it exists. Useful for demo purposes so that
// we can re-run this example.
if (client.IndexExists(pagesIndex).Exists)
    client.DeleteIndex(pagesIndex);

// create the index, adding the mapping for the Page type to the index
// at the same time. Automap() will infer the mapping from the POCO
var createIndexResponse = client.CreateIndex(pagesIndex, c => c
    .Mappings(m => m
        .Map<Page>(p => p
            .AutoMap()
        )
    )
);

看看有关如何控制 POCO 类型的映射的更多详细信息,请参阅自动映射文档

索引一个新的页面类型就像

Indexing a new Page type is as simple as

// create a sample Page
var page = new Page
{
    Title = "Sample Page",
    Body = "Sample Body",
    Category = "sample",
    Keywords = new List<string>
    {
        "sample",
        "example", 
        "demo"
    },
    LastUpdatedDate = DateTime.UtcNow,
    MetaDescription = "Sample meta description",
    SourceId = "1",
    Url = "/pages/sample-page"
};

// index the sample Page into Elasticsearch.
// NEST will infer the document type (_type) from the POCO type,
// by default it will camel case the POCO type name
var indexResponse = client.Index(page);

如果文档不存在,则索引文档将创建该文档,如果存在,则覆盖现有文档.Elasticsearch 有乐观并发控制,可以用来控制这在不同条件下的表现如何.

Indexing a document will create the document if it does not exist, or overwrite an existing document if it does exist. Elasticsearch has optimistic concurrency control that can be used to control how this behaves under different conditions.

我们可以使用 Update 方法更新文档,但首先要了解一些背景知识.

We can update a document using the Update methods, but first a little background.

我们可以通过指定索引、类型和 id 来从 Elasticsearch 中获取文档.NEST 使这稍微容易一些,因为我们可以从 POCO 推断出所有这些.当我们创建映射时,我们没有在 POCO 上指定 Id 属性;如果 NEST 看到一个名为 Id 的属性,它会使用它作为文档的 id 但因为我们没有,这不是问题,因为 Elasticsearch 会生成一个 id用于文档并将其放入文档元数据中.然而,因为文档元数据与源文档是分开的,所以这会使建模文档为 POCO 类型有点棘手(但并非不可能);对于给定的响应,我们将可以通过元数据访问文档的 id,并通过 _source 字段访问源.我们可以在应用程序中将 id 与我们的源结合起来.

We can get a document from Elasticsearch by specifying the index, type and id. NEST makes this slightly easier because we can infer all of these from the POCO. When we created our mapping, we didn't specify an Id property on the POCO; if NEST sees a property called Id, it uses this as the id for the document but because we don't have one, that's not a problem as Elasticsearch will generate an id for the document and put this in the document metadata. Because the document metadata is separate from the source document however, this can make modelling documents as POCO types a little trickier (but not impossible); for a given response, we will have access to the id of the document through the metadata and access to the source through the _source field. We can combine the id with our source in the application.

解决这个问题的一个更简单的方法是在 POCO 上设置一个 id.我们可以在 POCO 上指定一个 Id 属性,这将用作文档的 id,但如果我们不这样做,我们就不必调用属性 Id不想,如果不想,我们需要告诉 NEST 哪个属性代表 id.这可以通过属性来完成.假设 SourceIdPage 实例的唯一 ID,请使用 ElasticsearchTypeAttribute IdProperty 属性来指定它.也许我们不应该分析这个字符串而是逐字索引它,我们也可以通过属性上的属性的Index属性来控制这个

An easier way to address this though is to have an id on the POCO. We can specify an Id property on the POCO and this will be used as the id of the document, but we don't have to call the property Id if we don't want to and if we don't, we need to tell NEST which property represents the id. This can be done with an attribute. Assuming that SourceId is a unique id for a Page instance, use the ElasticsearchTypeAttribute IdProperty property to specify this. Maybe we shouldn't also analyze this string but index it verbatim, we can also control this through the Index property of the attribute on the property

[ElasticsearchType(IdProperty = nameof(SourceId))]
public class Page
{
    public string Url { get; set; }

    public string Title { get; set; }

    public string Body { get; set; }

    [String(Name="meta_description")]
    public string MetaDescription { get; set; }

    public IList<string> Keywords { get; set; }

    public string Category { get; set; }

    [Date(Name="last_updated_date")]
    public DateTimeOffset LastUpdatedDate { get; set; }

    [String(Name="source_id", Index=FieldIndexOption.NotAnalyzed)]
    public string SourceId { get; set; }
}

有了这些,我们需要像以前一样重新创建索引,以便这些更改反映在映射中,并且 NEST 可以在索引 Page 实例时使用此配置.

With these in place, we would need to recreate the index as before so that these changes are reflected in the mapping and NEST can use this configuration when indexing a Page instance.

现在,回到更新:) 我们可以从 Elasticsearch 获取一个文档,在应用程序中更新它,然后重新索引它

Now, back to updates :) We can get a document from Elasticsearch, update it in the application and then re-index it

var getResponse = client.Get<Page>("1");

var page = getResponse.Source;

// update the last updated date 
page.LastUpdatedDate = DateTime.UtcNow;

var updateResponse = client.Update<Page>(page, u => u.Doc(page));

第一个参数是我们想要获取的文档的 id,它可以由 NEST 从 Page 实例推断出来.由于我们将整个文档传回这里,我们可以使用 .Index() 而不是 Update(),因为我们正在更新所有字段

The first argument is the id for the document we want to get which can be inferred by NEST from the Page instance. Since we are passing the entire document back here, we could have just used .Index() instead of Update(), since we are updating all the fields

var indexResponse = client.Index(page);

但是,由于我们只想更新 LastUpdatedDate,因此必须从 Elasticsearch 中获取文档,在应用程序中更新它,然后将文档发送回 Elasticsearch,这是一项繁重的工作.我们可以只将更新的 LastUpdatedDate 发送到 Elasticsearch,而不是使用 partial 文档.C# 匿名类型在这里非常有用

However, since we only want to update the LastUpdatedDate, having to fetch the document from Elasticsearch, update it in the application, then send the document back to Elasticsearch is a lot of work. We can just send only the updated LastUpdatedDate to Elasticsearch instead using a partial document. C# anonymous types are really useful here

// model our partial document with an anonymous type. 
// Note that we need to use the snake casing name
// (NEST will still camel case the property names but this
//  doesn't help us here)
var lastUpdatedDate = new
{
    last_updated_date = DateTime.UtcNow
};

// do the partial update. 
// Page is TDocument, object is TPartialDocument
var partialUpdateResponse = client.Update<Page, object>("1", u => u
    .Doc(lastUpdatedDate)
);

如果需要使用RetryOnConflict(int)

var partialUpdateResponse = client.Update<Page, object>("1", u => u
    .Doc(lastUpdatedDate)
    .RetryOnConflict(1)
);

对于部分更新,Elasticsearch 将获取文档,应用部分更新,然后索引更新的文档;如果文档在获取和更新之间发生变化,Elasticsearch 将根据 RetryOnConflict(1) 再次重试.

With a partial update, Elasticsearch will get the document, apply the partial update and then index the updated document; if the document changes between getting and updating, Elasticsearch is going to retry this once more based on RetryOnConflict(1).

希望有帮助:)

这篇关于ElasticSearch Nest 插入/更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆