web-dev-qa-db-ja.com

ElasticSearchNestの挿入/更新

次のクエリを使用して、elasticでインデックスを作成しました。

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"
        }
      }
    }
  }
}

.netNESTライブラリを使用してこのインデックスにドキュメントを挿入したいと思います。私の問題は、.net更新メソッドの署名が私には意味をなさないということです。

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

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

NESTでは、TDocumentクラスとTPartialDocumentクラスはどこから来ていますか?私が作成したこれらのC#クラスは、インデックスを表していますか?

9
Andrew Walters

TDocumentTPartialDocumentは、POCOタイプのジェネリックタイプパラメーターです。

  • elasticsearch(TDocument)でドキュメントを表し、
  • 部分的な更新を実行するときの、Elasticsearch(TPartialDocument)内のドキュメントの一部の表現。

完全更新の場合、TDocumentTPartialDocumentは同じ具象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; }
}
_

デフォルトでは、NESTがPOCOプロパティをシリアル化するとき、キャメルケースの命名規則を使用します。インデックスには、いくつかのプロパティのスネークケースがあるためです。 _"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);
_

接続設定は、開発中に役立つ方法で構成されています。

  1. DefaultIndex()-デフォルトのインデックスは_"pages"_に設定されています。リクエストで明示的なインデックス名が渡されず、POCOのインデックス名を推測できない場合は、デフォルトのインデックスが使用されます。
  2. PrettyJson()-jsonのリクエストとレスポンスをプリティファイ(インデント)します。これは、Elasticsearchとの間で何が送受信されているかを確認するのに役立ちます。
  3. DisableDirectStreaming()-NESTは、デフォルトでPOCOを要求ストリームにシリアル化し、応答タイプを応答ストリームから逆シリアル化します。この直接ストリーミングを無効にすると、要求バイトと応答バイトがメモリストリームにバッファリングされ、OnRequestCompleted()でログアウトできるようになります。
  4. OnRequestCompleted()-応答を受信した後に呼び出されます。これにより、開発中にリクエストとレスポンスをログアウトできます。

2、3、および4は開発時に役立ちますが、パフォーマンスのオーバーヘッドが発生するため、本番環境では使用しないことを決定できます。

それでは、ページマッピングを使用してインデックスを作成しましょう

_// 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タイプのマッピングを制御する方法の詳細については、自動マッピングのドキュメントを参照してください

新しいページタイプのインデックス作成は、次のように簡単です。

_// 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には楽観的同時実行制御があります これを使用して、さまざまな条件下での動作を制御できます。

Updateメソッドを使用してドキュメントを更新できますが、最初は少し背景があります。

インデックス、タイプ、IDを指定することで、Elasticsearchからドキュメントを取得できます。 NESTは、これらすべてをPOCOから推測できるため、これを少し簡単にします。マッピングを作成したとき、POCOでIdプロパティを指定しませんでした。 NESTIdというプロパティを検出すると、これをドキュメントのIDとして使用しますが、プロパティがないため、ElasticsearchがドキュメントのIDを生成し、これをドキュメントのメタデータに入れます。ただし、ドキュメントのメタデータはソースドキュメントとは別のものであるため、POCOタイプとしてのモデリングドキュメントが少し複雑になる可能性があります(不可能ではありません)。特定の応答に対して、メタデータを介してドキュメントのIDにアクセスし、__source_フィールドを介してソースにアクセスできます。アプリケーションでIDとソースを組み合わせることができます。

ただし、これに対処する簡単な方法は、POCOにIDを設定することです。 POCOでIdプロパティを指定でき、これがドキュメントのIDとして使用されますが、必要がない場合はプロパティIdを呼び出す必要はありません。そうでない場合は、どのプロパティがIDを表すかをNESTに通知する必要があります。これは、属性を使用して実行できます。 SourceIdPageインスタンスの一意のIDであると仮定して、これを指定するにはElasticsearchTypeAttributeIdPropertyプロパティを使用します。この文字列を分析するのではなく、逐語的にインデックスを付ける必要があるかもしれません。プロパティの属性の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; }
}
_

これらを配置したら、これらの変更がマッピングに反映され、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));
_

最初の引数は、取得するドキュメントのIDであり、NESTによってPageインスタンスから推測できます。 entireドキュメントをここに返すので、.Index()の代わりにUpdate()を使用することもできます。すべてのフィールドを更新しているので

_var indexResponse = client.Index(page);
_

ただし、LastUpdatedDateのみを更新する必要があるため、Elasticsearchからドキュメントをフェッチし、アプリケーションで更新してから、ドキュメントをElasticsearchに返送する必要があります。 partialドキュメントを使用する代わりに、更新された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)
);
_

RetryOnConflict(int)を使用する必要がある場合は、ここで楽観的同時実行制御を使用できます。

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

部分的な更新では、Elasticsearchはドキュメントを取得し、部分的な更新を適用してから、更新されたドキュメントにインデックスを付けます。ドキュメントの取得と更新の間に変更があった場合、ElasticsearchはRetryOnConflict(1)に基づいてこれをもう一度再試行します。

お役に立てば幸いです:)

24
Russ Cam