ElasticSearch C# Nest 使用 5.1 获取热门词

ElasticSearch C# Nest Getting top words with 5.1

我有一个包含这些字段的 ElasticSearch object:

[Keyword]
public List<string> Tags { get; set; }
[Text]
public string Title { get; set; }

并且,在我使用此代码获取所有文档中的顶级标签之前:

var Match = Driver.Search<Metadata>(_ => _
                  .Query(Q => Q
                  .Term(P => P.Category, (int)Category)
                     && Q.Term(P => P.Type, (int)Type))
                  .FielddataFields(F => F.Fields(F1 => F1.Tags, F2 => F2.Title))
                  .Aggregations(A => A.Terms("Tags", T => T.Field(F => F.Tags)
                  .Size(Limit))));

但是对于 Elastic 5.1,我收到错误 400 提示:

Fielddata is disabled on text fields by default. Set fielddata=true on [Tags] in order to load fielddata in memory by uninverting the inverted index.

然后ES documentation about parameter mapping告诉你"It usually doesn’t make sense to do so"和"have a text field for full text searches, and an unanalyzed keyword field with doc_values enabled for aggregations"。

但是唯一的文档是针对 5.0 的,而针对 5.1 的相同页面似乎不存在。

现在,5.1 有一个关于 Term Aggregation 的页面,似乎涵盖了我需要的内容,但在 C# / Nest 中绝对找不到我可以使用的东西。

所以,我想弄清楚如何从标签中获取所有文档中的热门词(其中每个标签都是自己的词;例如 "New York" 不是 "New""York")和标题(每个词都是自己的东西)在 C# 中。


我需要编辑这个 post 因为似乎有更深层次的问题。我写了一些测试代码来说明这个问题:

让我们创建一个简单的 object:

public class MyObject
{
    [Keyword]
    public string Id { get; set; }
    [Text]
    public string Category { get; set; }
    [Text(Fielddata = true)]
    public string Keywords { get; set; }
}

创建索引:

var Uri = new Uri(Constants.ELASTIC_CONNECTIONSTRING);
var Settings = new ConnectionSettings(Uri)
.DefaultIndex("test")
.DefaultFieldNameInferrer(_ => _)
.InferMappingFor<MyObject>(_ => _.IdProperty(P => P.Id));   
var D = new ElasticClient(Settings);

用随机的东西填充索引:

for (var i = 0; i < 10; i++)
{
    var O = new MyObject
    {
        Id = i.ToString(),
        Category = (i % 2) == 0 ? "a" : "b",
        Keywords = (i % 3).ToString()
    };

    D.Index(O);
}

并进行查询:

var m = D.Search<MyObject>(s => s
    .Query(q => q.Term(P => P.Category, "a"))
    .Source(f => f.Includes(si => si.Fields(ff => ff.Keywords)))
    .Aggregations(a => a
        .Terms("Keywords", t => t
            .Field(f => f.Keywords)
            .Size(Limit)
        )
    )
);

它和以前一样失败,有一个 400 和:

Fielddata is disabled on text fields by default. Set fielddata=true on [Keywords] in order to load fielddata in memory by uninverting the inverted index.

但是 [Keywords] 上的 Fielddata 设置为 true,但它一直抱怨它。

所以,让我们疯狂地修改 class 这样:

public class MyObject
{
    [Text(Fielddata = true)]
    public string Id { get; set; }
    [Text(Fielddata = true)]
    public string Category { get; set; }
    [Text(Fielddata = true)]
    public string Keywords { get; set; }
}

这样,所有内容都是文本,所有内容都具有 Fielddata = true.. 嗯,结果相同。

所以,要么我真的不理解简单的东西,要么它坏了或没有记录:)

你想要 Fielddata; for your particular search here where you want to return just the tags and the title fields from the search query, take a look at using Source Filtering 的情况并不常见

var Match = client.Search<Metadata>(s => s
    .Query(q => q
        .Term(P => P.Category, (int)Category) && q
        .Term(P => P.Type, (int)Type)
    )
    .Source(f => f
        .Includes(si => si
            .Fields(
                ff => ff.Tags, 
                ff => ff.Title
            )
        )
    )
    .Aggregations(a => a
        .Terms("Tags", t => t
            .Field(f => f.Tags)
            .Size(Limit)
        )
    )
);

Fielddata 需要 uninvert 将倒排索引放入内存结构中以进行聚合和排序。虽然访问此数据的速度非常快,但对于大型数据集,它也会消耗大量内存。

编辑:

在您的编辑中,我没有看到您在任何地方创建 索引并明确映射您的 MyObject POCO;无需显式创建索引和映射 POCO,Elasticsearch 将自动创建索引并根据收到的第一个 json 文档推断 MyObject 的映射,这意味着 Keywords 将被映射为具有 keyword multi_field 和 Fielddata 的 text 字段将不会在 text 字段映射上启用。

这是一个演示它全部工作的例子

void Main()
{
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
    var defaultIndex = "test";
    var connectionSettings = new ConnectionSettings(pool)
            .DefaultIndex(defaultIndex)
            .DefaultFieldNameInferrer(s => s)
            .InferMappingFor<MyObject>(m => m
                .IdProperty(p => p.Id)
            );

    var client = new ElasticClient(connectionSettings);

    if (client.IndexExists(defaultIndex).Exists)
        client.DeleteIndex(defaultIndex);

    client.CreateIndex(defaultIndex, c => c
        .Mappings(m => m
            .Map<MyObject>(mm => mm
                .AutoMap()
            )
        )
    );

    var objs = Enumerable.Range(0, 10).Select(i =>
        new MyObject
        {
            Id = i.ToString(),
            Category = (i % 2) == 0 ? "a" : "b",
            Keywords = (i % 3).ToString()
        });

    client.IndexMany(objs);

    client.Refresh(defaultIndex);

    var searchResponse = client.Search<MyObject>(s => s
        .Query(q => q.Term(P => P.Category, "a"))
        .Source(f => f.Includes(si => si.Fields(ff => ff.Keywords)))
        .Aggregations(a => a
            .Terms("Keywords", t => t
                .Field(f => f.Keywords)
                .Size(10)
            )
        )
    );

}

public class MyObject
{
    [Keyword]
    public string Id { get; set; }
    [Text]
    public string Category { get; set; }
    [Text(Fielddata = true)]
    public string Keywords { get; set; }
}

这个returns

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 5,
    "max_score" : 0.9808292,
    "hits" : [
      {
        "_index" : "test",
        "_type" : "myobject",
        "_id" : "8",
        "_score" : 0.9808292,
        "_source" : {
          "Keywords" : "2"
        }
      },
      {
        "_index" : "test",
        "_type" : "myobject",
        "_id" : "0",
        "_score" : 0.2876821,
        "_source" : {
          "Keywords" : "0"
        }
      },
      {
        "_index" : "test",
        "_type" : "myobject",
        "_id" : "2",
        "_score" : 0.13353139,
        "_source" : {
          "Keywords" : "2"
        }
      },
      {
        "_index" : "test",
        "_type" : "myobject",
        "_id" : "4",
        "_score" : 0.13353139,
        "_source" : {
          "Keywords" : "1"
        }
      },
      {
        "_index" : "test",
        "_type" : "myobject",
        "_id" : "6",
        "_score" : 0.13353139,
        "_source" : {
          "Keywords" : "0"
        }
      }
    ]
  },
  "aggregations" : {
    "Keywords" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "0",
          "doc_count" : 2
        },
        {
          "key" : "2",
          "doc_count" : 2
        },
        {
          "key" : "1",
          "doc_count" : 1
        }
      ]
    }
  }
}

您还可以考虑将 Keywords 映射为具有 keyword multi_field 的 text 字段,使用 text 字段进行非结构化搜索,并且 keyword 用于排序、聚合和结构化搜索。这样,您就可以两全其美,并且不需要启用 Fielddata

client.CreateIndex(defaultIndex, c => c
    .Mappings(m => m
        .Map<MyObject>(mm => mm
            .AutoMap()
            .Properties(p => p
                .Text(t => t
                    .Name(n => n.Keywords)
                    .Fields(f => f
                        .Keyword(k => k
                            .Name("keyword")
                        )
                    )
                )
            )
        )
    )
);

然后在搜索中使用

var searchResponse = client.Search<MyObject>(s => s
    .Query(q => q.Term(P => P.Category, "a"))
    .Source(f => f.Includes(si => si.Fields(ff => ff.Keywords)))
    .Aggregations(a => a
        .Terms("Keywords", t => t
            .Field(f => f.Keywords.Suffix("keyword"))
            .Size(10)
        )
    )
);