Elasticsearch 6.6 和 NEST 6.6 中带有排序和分页变量的 SearchTemplate

SearchTemplate with sorting and paging variables in Elasticsearch 6.6 and NEST 6.6

全部, 我正在尝试调用 ES 6.6 中定义的 SearchTemplate。该模板具有分页变量(来自和大小)和我在数组中传递的电子邮件。这也具有使用自定义脚本逻辑进行排序的功能。当我 运行 在 kibana 中执行此操作时,我看不到分页和排序不起作用。我将不胜感激任何帮助让它发挥作用的帮助。请参阅下面的详细信息。我使用索引别名搜索了两个索引。

person 和 guest 索引的映射相同(只是为了简化示例)

索引映射

PUT _template/person_guest_template
{
  "order": 0,
  "index_patterns": ["person*","guest*"],
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "autoComplete_filter": {
            "type": "edge_ngram",
            "min_gram": "2",
            "max_gram": "20"
          }
        },
        "analyzer": {
          "autoComplete": {
            "filter": ["lowercase", "asciifolding","autoComplete_filter"],
            "type": "custom",
            "tokenizer": "whitespace"
          },
          "default": {
            "filter": ["lowercase", "asciifolding"],
            "type": "custom",
            "tokenizer": "whitespace"
          }
        }
      },
      "number_of_shards": "3",
      "number_of_replicas": "1"
    }
  },
  "mappings": {
    "_doc": {
      "dynamic": false,
      "properties": {
        "firstName": {
          "type": "keyword",
          "fields": {
            "search": {
              "type": "text",
              "analyzer": "autoComplete",
              "search_analyzer": "default"
            }
          }
        },
        "lastName": {
          "type": "keyword",
          "fields": {
            "search": {
              "type": "text",
              "analyzer": "autoComplete",
              "search_analyzer": "default"
            }
          }
        },
        "email": {
          "type": "keyword"
        },"email": {
      "type": "keyword"
    }
      }
    }
  }
}

搜索模板定义

POST _scripts/guest_person_by_email
{
  "script": {
    "from": "{{from}}{{^from}}0{{/from}}",
    "size": "{{size}}{{^size}}5{{/size}}",
    "sort": [
      {
        "_script": {
          "order": "asc",
          "type": "number",
          "script": "return (doc['type'].value == 'person')? 0 : 1;"
        }
      },
      {
        "firstName": {
          "order": "asc"
        }
      },
      {
        "lastName": {
          "order": "asc"
        }
      }
    ],
    "lang": "mustache",
    "source": """
    {
      "query":{
        "bool":{
          "filter":{
            "terms":{
              "email":
              {{#toJson}}emails{{/toJson}}
            }
          }
        }
      }
    }
"""
  }
}

使用 SearchTemplate 进行搜索

GET guest-person/_search/template
{
  "id":"guest_person_by_email",
  "params": {
    "emails":["rennishj@test.com"]
  }
}

示例数据

PUT person/_doc/1
{
  "firstName": "Rennish",
  "lastName": "Joseph",
  "email": [
    "rennishj@test.com"
  ],
  "type":"person"
}

使用 NEST 6.6 调用搜索模板

List<string> emails = new List<string>(){"rennishj@test.com"};
var searchResponse = client.SearchTemplate<object>(st => st
    .Index("guest-person")
    .Id("guest_person_by_email")
    .Params(p => p
        .Add("emails", emails.ToArray())
        .Add("from", 0)     
        .Add("size", 50)
    )
);

观察结果

  1. 当我从搜索模板中删除 from、size 和 sort 逻辑时,它起作用了
  2. 好像我把排序和 from/size 变量放在了错误的地方?

我在这里 post 找到了类似的 https://discuss.elastic.co/t/c-nest-5-search-with-template/104074/2,但 GetSearchTemplate 和 PutSearchTemplate 似乎已在 NEST 6.x

上停用

这可以使用搜索模板来完成吗?我们使用一些非常复杂的 NEST 查询,并且正在远离 NEST 并使用搜索模板。

有几个问题

  1. 索引模板两次定义"email"字段映射
  2. 索引模板将 "dynamic" 设置为 false 但不包含 "type" 字段映射,因此脚本排序将失败
  3. 需要在 "source" 内为 Put 脚本 API 调用定义整个搜索请求

NEST 有助于构建正确的搜索请求并将它们用作搜索模板的基础,此外还有一大堆使用客户端的原因,如循环请求、自动故障转移和重试等。

这是一个完整的例子

private static void Main()
{
    var defaultIndex = "person";
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var settings = new ConnectionSettings(pool)
        .DefaultIndex(defaultIndex)
        .DefaultTypeName("_doc");

    var client = new ElasticClient(settings);

    // WARNING: This deletes the index to make this code repeatable.
    // You probably want to remove this if copying verbatim
    if (client.IndexExists(defaultIndex).Exists)
        client.DeleteIndex(defaultIndex);

    var indexTemplateResponse = client.LowLevel.IndicesPutTemplateForAll<PutIndexTemplateResponse>(
        "person_guest_template",
        @"{
          ""order"": 0,
          ""index_patterns"": [""person*"",""guest*""],
          ""settings"": {
            ""index"": {
              ""analysis"": {
                ""filter"": {
                  ""autoComplete_filter"": {
                    ""type"": ""edge_ngram"",
                    ""min_gram"": ""2"",
                    ""max_gram"": ""20""
                  }
                },
                ""analyzer"": {
                  ""autoComplete"": {
                    ""filter"": [""lowercase"", ""asciifolding"",""autoComplete_filter""],
                    ""type"": ""custom"",
                    ""tokenizer"": ""whitespace""
                  },
                  ""default"": {
                    ""filter"": [""lowercase"", ""asciifolding""],
                    ""type"": ""custom"",
                    ""tokenizer"": ""whitespace""
                  }
                }
              },
              ""number_of_shards"": ""3"",
              ""number_of_replicas"": ""1""
            }
          },
          ""mappings"": {
            ""_doc"": {
              ""dynamic"": false,
              ""properties"": {
                ""firstName"": {
                  ""type"": ""keyword"",
                  ""fields"": {
                    ""search"": {
                      ""type"": ""text"",
                      ""analyzer"": ""autoComplete"",
                      ""search_analyzer"": ""default""
                    }
                  }
                },
                ""lastName"": {
                  ""type"": ""keyword"",
                  ""fields"": {
                    ""search"": {
                      ""type"": ""text"",
                      ""analyzer"": ""autoComplete"",
                      ""search_analyzer"": ""default""
                    }
                  }
                },
                ""email"": {
                  ""type"": ""keyword""
                },
                ""type"": {
                  ""type"": ""keyword""
                }
              }
            }
          }
        }");

    // build a prototype search request     
    var searchRequest = new SearchRequest
    {
        From = 0,
        Size = 0,
        Sort = new List<ISort> 
        {
            new ScriptSort
            {
                Order = Nest.SortOrder.Ascending,
                Type = "number",
                Script = new InlineScript("return (doc['type'].value == 'person')? 0 : 1;")
            },
            new SortField
            {
                Field = "firstName",
                Order = Nest.SortOrder.Ascending
            },
            new SortField
            {
                Field = "lastName",
                Order = Nest.SortOrder.Ascending
            }
        },
        Query = new BoolQuery
        {
            Filter = new QueryContainer[] 
            {
                new TermsQuery
                {
                    Field = "email",
                    Terms = new[] { "emails" }
                }
            }
        }
    };

    var json = client.RequestResponseSerializer.SerializeToString(searchRequest);
    // create template from prototype search request
    var jObject = JsonConvert.DeserializeObject<JObject>(json); 
    jObject["from"] = "{{from}}{{^from}}0{{/from}}";
    jObject["size"] = "{{size}}{{^size}}5{{/size}}";    
    json = jObject.ToString(Newtonsoft.Json.Formatting.None);
    // below is invalid JSON, so can only be constructed with replacement
    json = json.Replace("[\"emails\"]", "{{#toJson}}emails{{/toJson}}");

    // add search template
    var putScriptResponse = client.PutScript("guest_person_by_email", s => s
        .Script(sc => sc
            .Lang(ScriptLang.Mustache)
            .Source(json)
        )
    );

    var person = new Person
    {
        FirstName = "Rennish",
        LastName = "Joseph",
        Email = new[] { "rennishj@test.com" }
    };

    // index document
    var indexResponse = client.Index(person, i => i.Id(1).Refresh(Refresh.WaitFor));

    // search
    var searchResponse = client.SearchTemplate<Person>(s => s
        .Id("guest_person_by_email")
        .Params(p => p
            .Add("emails", person.Email)
            .Add("from", 0)
            .Add("size", 50)
        )
    );
}

public class Person 
{
    public string FirstName {get;set;}
    public string LastName { get; set; }
    public string[] Email {get;set;}
    public string Type {get; set;} = "person";
}

搜索模板请求的结果是

{
  "took" : 47,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "person",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "firstName" : "Rennish",
          "lastName" : "Joseph",
          "email" : [
            "rennishj@test.com"
          ],
          "type" : "person"
        },
        "sort" : [
          0.0,
          "Rennish",
          "Joseph"
        ]
      }
    ]
  }
}

添加正确的 SearchTemplate(如 Russ Cam 指出的那样,在“source”下移动分页和排序)以防将来有人需要它。

POST _scripts/guest_person_by_email
{
  "script": {    
    "lang": "mustache",
    "source": """
     {
       "from": "{{from}}{{^from}}0{{/from}}",
       "size": "{{size}}{{^size}}5{{/size}}",
      "sort": [
      {
        "_script": {
          "order": "asc",
          "type": "number",
          "script": "return (doc['type'].value == 'person')? 0 : 1;"
        }
      },
      {
        "firstName": {
          "order": "asc"
        }
      },
      {
        "lastName": {
          "order": "asc"
        }
      }
    ],
      "query":{
        "bool":{
          "filter":{
            "terms":{
              "email":
              {{#toJson}}emails{{/toJson}}
            }
          }
        }
      }
    }
"""
  }
}