【ElasticSearch】bulkAPIを用いたデータ登録および検索をC#で試してみた

# はじめに
ElasticSearchを使ってみたい！という興味方位からElasticSearch導入～Webアプリケーションにて検索までを実施してみた。
ほぼほぼ初心者なのでお手柔らかにお願いします。
※今回はIndex定義やMapping定義などをせず、とりあえず検索したいというレベルで開発しています。

# Elastic Search導入編
下記URLを参考にElasticSearchとKibanaをインストールする
https://qiita.com/azumabashi/items/a6d989f02bf0e369023a
> Elasticsearch と Kibana 7.5.1 Windows をインストールしてみる

とりあえず使いたいレベルであればデータ登録時にIndexが自動生成されるっぽい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306026/c41da031-eebb-c549-449e-e330dbae6eca.png)


データ登録をKibanaで一つずつ作るのは面倒くさいので、C#のConsoleアプリケーションをVisual Studioで開発する。
データ登録は下記URLにあるbulkAPIを用いる
https://qiita.com/mino_s2000/items/191817f9d1d16320c478
> NEST Tips - Elasticsearch .NET Client

# 開発方針
下記方針で開発を行った。
前提：タイトルからURLを取得するといったツールを作成する
①Consoleアプリケーションにて登録したいデータをcsvからデータ取得し、bulkAPIを用いてIndexに投入する
②MVC WebアプリケーションにてElasticSearchを用いて検索を行い、画面に検索結果を表示させる。


実際に作成したコードを下記に記載する

# バッチ

```C#:Program.cs
// 検索用（登録用のバッチのため不要）
//ElasticSearch.SearchData("API");

// 登録用
// CSVからデータ取得
var list = FileOperationService.GetCsvFileForElasticSearchData();
ElasticSearch.RegistData(list);
```

```C#:ElasticSearch.cs
static class ElasticSearch
{
    public static string ApiUrl = "http://localhost:9200";
    public static string IndexName = "testindex";
    public static ConnectionSettings settings = new ConnectionSettings(new Uri(ApiUrl));
    public static ElasticClient client = new ElasticClient(settings.DefaultIndex(IndexName));


    public static void SearchData(string searchWord)
    {
        IList<IndexModel> hits = new List<IndexModel>();

        try
        {
            var response = client.Search<IndexModel>(s => s.Size(10)
            .Query(q => q.MatchPhrasePrefix(m => m.Field(f => f.title).Query(searchWord))));

            foreach (var hit in response.Hits)
            {
                hits.Add(hit.Source);
                Console.WriteLine("id:" + hit.Source.id);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        Console.WriteLine("検索結果：" + hits.Count);
        Console.WriteLine("何かキーを押してください");
        Console.ReadKey();
    }

    public static void RegistData(List<IndexModel> indexModel)
    {
        var docList = new List<IndexModel>();
        foreach (var index in indexModel)
        {
            docList.Add(new IndexModel
            {
                id = index.id,
                title = index.title,
                path = index.path,
                description = index.description
            }
            );
        }
        var res = client.Bulk(e => e
                .Index(IndexName)
                .IndexMany(docList));

    }
}
```

```C#:IndexModel.cs
namespace ConsoleElasticSearchSample.Model
{
    class IndexModel
    {
        public string id { get; set; }
        public string title { get; set; }
        public string path { get; set; }
        public string description { get; set; }
    }
}
```
```C#:FileOperationService.cs
static class FileOperationService
{
    public static string FilePath = @"C:\work\elasticsearch\";
    public static List<IndexModel> GetCsvFileForElasticSearchData()
    {
        // 配列からリストに格納する
        var indexList = new List<IndexModel>();

        // 読み込みたいCSVファイルのパスを指定して開く
        using (var sr = new StreamReader(FilePath + "elasticsearch.csv"))
        {
            // 末尾まで繰り返す
            while (!sr.EndOfStream)
            {
                // CSVファイルの一行を読み込む
                string line = sr.ReadLine();
                // 読み込んだ一行をカンマ毎に分けて配列に格納する
                string[] values = line.Split(',');

                var index = new IndexModel();
                index.id = values[0];
                index.title = values[1];
                index.path = values[2];
                index.description = values[3];
                indexList.Add(index);
            }
        }

        return indexList;
    }
}
```

# オンライン
## 共通処理はバッチを参照
```C#:HomeController.cs
public ActionResult About()
{
    ViewBag.Message = "ElasticSearchの結果を表示します";
    var list = ElasticSearch.SearchData("ela");
    ViewBag.list = list;

    return View();
}
```

```html:About.cshtml
@{
    ViewBag.Title = "About";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

<p>
    <table class="table table-responsive">
        <tr>
            <th>id</th>
            <th>title</th>
            <th>path</th>
            <th>description</th>
        </tr>
        @for (var i = 0; i < ViewBag.list.Count; i++)
        {
    <tr>
        @{
            <th>@ViewBag.list[i].id</th>
            <th>@ViewBag.list[i].title</th>
            <th><a href="@ViewBag.list[i].path" target="_blank">@ViewBag.list[i].path</th>
            <th>@ViewBag.list[i].description</th>
        }

    </tr>
        }
    </table>

</p>
```
# CSV
```csv:elasticsearch.csv
id,title,path,description
1,Elasticsearch と Kibana 7.5.1 Windows をインストールしてみる,https://qiita.com/azumabashi/items/a6d989f02bf0e369023a,https://db-engines.com/en/ranking によると、サーチエンジンのElasticsearchはかなりいいところ（2019年12月付で全体の8位、サーチエンジンではトップ）につけています。
2,NEST Tips - Elasticsearch .NET Client,https://qiita.com/mino_s2000/items/191817f9d1d16320c478,川崎フロンターレ、J1初優勝おめでとうございます！等々力に行きたかったです。
3,Python Elasticsearch 基本的な使い方まとめ,https://qiita.com/satto_sann/items/8a63761bbfd6542bb9a2,PythonでElasticsearchを使う機会があったため情報を収集していましたが、サイトで使われているElasticsearchのバージョンが古かったり、そもそも、情報が少なかったりしたので、今回、メモとして簡単な例と共に基本的な使い方をまとめました。
```

# 開発メモ
bulkAPIには「NEST」を使用しました。
NESTをVisual Studioで使用するためには、Nugetパッケージから「NEST」で検索を行い、最新バージョンをインストールすることで使用することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306026/640413b3-5d2c-c99f-ce99-846f1a7495af.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306026/68eea2cb-49fd-8a02-36ed-52ede96a36bc.png)


# 暫定版画面
titleフィールドに登録されている「ela」を検索した結果が下記となる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306026/4a18143e-543f-4f43-d994-adf5d3b09f4c.png)
入力エリアを用意して、AjaxにてElasticSearchを呼び出す仕組みを用意するとよりよいだろう。

# ElasticSearchのメモ
今回検索にて「elastic」などで検索してもヒットしない現象が起きました。

```json
GET testindex/_search
{
  "query" : {
    "match": {
      "title": "elastic"
    }
  }
}
```

ElasticSearchでは"match"は完全一致検索となるため、部分一致検索をしたい場合は"match_phrase_prefix"を使用しないといけないみたいです。
知らなかった。。。

```json
GET testindex/_search
{
  "query" : {
    "match_phrase_prefix": {
      "title": "elastic"
    }
  }
}
```
上記であれば検索可能。

# 最後に
今後はIndex、Mapping定義をしっかりと実施したいのと、ElasticSearchの性能検証など試してみる。
あとは検索クエリのまとめとかやってみようかなーと。