ElasticSearch嵌套插入/更新

And*_*ers 9 elasticsearch nest

我使用以下查询创建了弹性索引:

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"
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

client.Update<TDocument>(IUpdateRequest<TDocument,TPartialDocument>)
Run Code Online (Sandbox Code Playgroud)

Java库对我来说更有意义:

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();
Run Code Online (Sandbox Code Playgroud)

在NEST哪里TDocumentTPartialDocument班级来自哪里?这些C#类是否代表我的索引?

Rus*_*Cam 24

TDocument并且TPartialDocument是POCO类型的通用类型参数

  • 代表Elasticsearch(TDocument)和中的文档
  • TPartialDocument执行部分更新时Elasticsearch()中文档的一部分的表示.

在完全更新的情况下,TDocument并且TPartialDocument可以指代相同的混凝土POCO类型.我们来看一些示例来演示.

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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} \n" +
                        $"{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}\n" +
                             $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" +
                             $"{new string('-', 30)}\n");
                }
                else
                {
                    Console.WriteLine($"Status: {response.HttpStatusCode}\n" +
                             $"{new string('-', 30)}\n");
                }
            });

var client = new ElasticClient(connectionSettings);
Run Code Online (Sandbox Code Playgroud)

连接设置的配置方式在开发过程中很有用;

  1. DefaultIndex()- 默认索引已配置为"pages".如果没有在请求上传递显式索引名称,并且不能为POCO推断索引名称,则将使用默认索引.
  2. PrettyJson() - Prettify(即缩进)json请求和响应.这对于查看Elasticsearch发送和接收的内容非常有用.
  3. DisableDirectStreaming() - 默认情况下,NEST将POCO序列化为请求流,并从响应流中反序列化响应类型.禁用此直接流将缓冲内存流中的请求和响应字节,允许我们将其注销OnRequestCompleted()
  4. OnRequestCompleted() - 收到回复后调用.这使我们能够在开发过程中注销请求和响应.

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

现在,让我们使用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()
        )
    )
);
Run Code Online (Sandbox Code Playgroud)

查看自动化文档,了解有关如何控制POCO类型映射的更多详细信息

索引新的页面类型非常简单

// 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);
Run Code Online (Sandbox Code Playgroud)

索引文档将创建文档(如果文档不存在),或覆盖现有文档(如果存在).Elasticsearch具有乐观并发控制,可用于控制在不同条件下的行为.

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

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

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

[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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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));
Run Code Online (Sandbox Code Playgroud)

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

var indexResponse = client.Index(page);
Run Code Online (Sandbox Code Playgroud)

但是,由于我们只想更新LastUpdatedDate,不得不从Elasticsearch获取文档,在应用程序中更新它,然后将文档发送回Elasticsearch是很多工作.我们只能LastUpdatedDate使用部分文档将更新的内容发送到Elasticsearch .C#匿名类型在这里非常有用

// 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)
);
Run Code Online (Sandbox Code Playgroud)

如果我们需要使用,我们可以在这里使用乐观并发控制 RetryOnConflict(int)

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

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

希望有帮助:)