Elasticsearch(十)【NEST高级客户端–搜索查询】

  • Post author:
  • Post category:其他


搜索

Search API允许您执行搜索查询并获取与查询匹配的搜索匹配。 Elasticsearch的搜索功能可能是您使用它的原因之一,NEST公开了所有可用的不同类型的搜索,以及一些聪明的使用Elasticsearch从强类型语言更容易使用。

 编写查询
 编写布尔查询
 仅返回某些字段
 协变检索结果

编写查询

在Elasticsearch中建立数据索引后,您将能够搜索它。 Elasticsearch提供强大的查询DSL来定义查询以再次执行Elasticsearch。 这个DSL是基于JSON的,并且在NEST中以Fluent API和Object Initializer语法的形式公开

匹配所有查询

最简单的查询是match_all查询; 这将返回所有文档,给它们一个

_score

为1.0

并非所有匹配的文档都在一个响应中返回; 默认情况下,只返回前十个文档。 您可以使用from和size来分页结果。

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .MatchAll()
    )
);

其序列化到以下JSON

{
  "query": {
    "match_all": {}
  }
}

由于

match_all

查询是常见的,所以前面的示例也有一个简短的序列化到相同的查询DSL JSON

searchResponse = client.Search<Project>(s => s
    .MatchAll()
);

前面两个例子都使用Fluent API来表达查询。 NEST还公开了一个Object Initializer语法来编写查询

var searchRequest = new SearchRequest<Project>
{
    Query = new MatchAllQuery()
};

searchResponse = client.Search<Project>(searchRequest);

常见查询

默认情况下,文档将按

_score

降序返回,其中每个匹配的

_score

是为文档与查询条件匹配的程度计算的相关性分数。

您可以使用许多搜索查询,所有这些都在

Query DSL

参考部分中记录。 在这里,我们要强调用户通常要执行的三种查询操作类型

 结构化搜索
 非结构化搜索
 组合查询

结构化搜索

结构化搜索是关于具有固有结构的查询数据。日期,时间和数字都是结构化的,通常要查询这些类型的字段以查找精确匹配,属于范围内的值等。文本也可以被结构化,例如,应用于博客文章的关键字标签

通过结构化搜索,查询的答案总是yes 或no ; 一个文件是查询的匹配项,或者不是。

词条查询通常用于结构化搜索。 以下是一个示例,其中查找日期开始在指定范围内的文档:

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .DateRange(r => r
            .Field(f => f.StartedOn)
            .GreaterThanOrEquals(new DateTime(2017, 01, 01))
            .LessThan(new DateTime(2018, 01, 01))
        )
    )
);

它产生以下查询JSON:

{
  "query": {
    "range": {
      "startedOn": {
        "lt": "2018-01-01T00:00:00",
        "gte": "2017-01-01T00:00:00"
      }
    }
  }
}

由于这个查询的答案总是yes或 no,我们不想对查询进行评分。 为此,我们可以通过在

bool

查询的

filter

子句中包含它,来获取在过滤器上下文中执行的查询

searchResponse = client.Search<Project>(s => s
   .Query(q => q
       .Bool(b => b
           .Filter(bf => bf
               .DateRange(r => r
                   .Field(f => f.StartedOn)
                   .GreaterThanOrEquals(new DateTime(2017, 01, 01))
                   .LessThan(new DateTime(2018, 01, 01))
               )
           )
       )

   )
);
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "startedOn": { "lt": "2018-01-01T00:00:00", "gte": "2017-01-01T00:00:00" } }
        }
      ]
    }
  }
}

在过滤器上下文中执行查询的好处是Elasticsearch能够放弃计算相关性分数,以及缓存过滤器以便更快的后续性能。

词条查询没有分析阶段,即查询输入未被分析,并且在反向索引中查找与输入的精确匹配。 当对索引时分析的字段使用词条查询时,这可能难倒许多学习新手。

当字段仅用于精确匹配时,您应该将其作为关键字数据类型进行索引。 如果一个字段用于精确匹配和全文搜索,则应考虑将其与多个字段进行索引。

非结构化搜索

另一个常见的用例是在全文字段中搜索,以便找到最相关的文档。

全文查询用于非结构化搜索; 这里我们使用

match

查询来查找 在主要的开发者名字字段中包含

"Russ"

的所有文档,

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .Match(m => m
            .Field(f => f.LeadDeveloper.FirstName)
            .Query("Russ")
        )
    )
);

它产生以下查询JSON

{
  "query": {
    "match": {
      "leadDeveloper.firstName": {
        "query": "Russ"
      }
    }
  }
}

全文查询具有分析阶段,即分析查询输入,并将来自查询分析的结果项与倒排索引中的项进行比较。

您可以完全控制在搜索时间和索引时间应用的分析,通过映射将

analyzers

应用于

text

数据类型字段。

组合查询

一个非常常见的情况是将单独的查询组合在一起形成复合查询,其中最常见的是

bool

查询:

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .Bool(b => b
            .Must(mu => mu
                .Match(m => m 
                    .Field(f => f.LeadDeveloper.FirstName)
                    .Query("Russ")
                ), mu => mu
                .Match(m => m 
                    .Field(f => f.LeadDeveloper.LastName)
                    .Query("Cam")
                )
            )
            .Filter(fi => fi
                 .DateRange(r => r
                    .Field(f => f.StartedOn)
                    .GreaterThanOrEquals(new DateTime(2017, 01, 01))
                    .LessThan(new DateTime(2018, 01, 01)) 
                )
            )
        )
    )
);

它产生以下查询JSON

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "leadDeveloper.firstName": { "query": "Russ" } }
        },
        {
          "match": {
            "leadDeveloper.lastName": { "query": "Cam" } }
        }
      ],
      "filter": [
        {
          "range": {
            "startedOn": { "lt": "2018-01-01T00:00:00", "gte": "2017-01-01T00:00:00" } }
        }
      ]
    }
  }
}

文档必须满足本示例中的所有三个查询才能匹配

 因为这两个查询都是在查询上下文中运行的,所以名称和姓氏的`match`查询将有助于计算相关性分数
 针对启动日期的`range`查询正在过滤器上下文中运行,因此不会为匹配文档计算得分(所有文档对于该查询具有相同的分数1.0)。

因为

bool

查询是如此常见,NEST重载操作符查询以使形成

bool

查询更加简洁。 以前的

bool

查询可以更简洁地表示为

searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .Match(m => m
            .Field(f => f.LeadDeveloper.FirstName)
            .Query("Russ")
        ) && q 
        .Match(m => m
            .Field(f => f.LeadDeveloper.LastName)
            .Query("Cam")
        ) && +q 
        .DateRange(r => r
            .Field(f => f.StartedOn)
            .GreaterThanOrEquals(new DateTime(2017, 01, 01))
            .LessThan(new DateTime(2018, 01, 01))
        )
    )
);

使用二进制

&&

运算符组合查询 在

bool

query

filter子句中使用一元

+

运算符包装查询,并使用二进制

&&

运算符进行合并

搜索响应

从搜索查询返回的响应是

ISearchResponse<T>

,其中T是在搜索方法调用中定义的通用参数类型。 响应中有很少的属性,但最常见的可能是使用的是

.Documents

,我们将在下面进行说明。

匹配文件

要获得匹配搜索查询的响应中的文档是很容易的

var searchResponse = client.Search<Project>(s => s
    .Query(q => q
        .MatchAll()
    )
);

var projects = searchResponse.Documents;


.Documents

是一个方便的简写

searchResponse.HitsMetaData.Hits.Select(h => h.Source);

并且可以从命中集合中检索关于每个命中的其他元数据。 以下是使用突出显示来检索匹配的高亮的示例

var highlights = searchResponse.HitsMetaData.Hits.Select(h => h
    .Highlights 
);

编写布尔查询

使用查询DSL时,编写

bool

查询可能会变得更加冗长。 例如,使用两个should子句进行单个bool查询

var searchResults = this.Client.Search<Project>(s => s
    .Query(q => q
        .Bool(b => b
            .Should(
                bs => bs.Term(p => p.Name, "x"),
                bs => bs.Term(p => p.Name, "y")
            )
        )
    )
);

现在,想象多个嵌套的布尔查询; 你会意识到,这很快就成为了一个hadouken缩进的练习

运算符重载

因此,NEST引入了运算符重载,所以复杂的bool查询变得更容易编写。 重载的操作符是

 二进制||运算符
 二进制&&运算符
 一元!运算符
 一元+运算符

我们将演示每个例子。

二进制||运算符

使用重载二进制

||

运算符,可以更简洁地表达一个带有

should

子句的

bool

查询。

以前的例子现在变成了Fluent API

var firstSearchResponse = client.Search<Project>(s => s
    .Query(q => q.Term(p => p.Name, "x") || q.Term(p => p.Name, "y")
    )
);

并使用对象初始化程序语法

var secondSearchResponse = client.Search<Project>(new SearchRequest<Project>
{
    Query = new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } ||
            new TermQuery { Field = Field<Project>(p => p.Name), Value = "y" }
});

两者都产生以下JSON查询DSL

{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "name": { "value": "x" } }
        },
        {
          "term": {
            "name": { "value": "y" } }
        }
      ]
    }
  }
}

二进制&&运算符

重载的二进制

&&

运算符可用于将查询组合在一起。 当要组合的查询没有应用任何一元运算符时,生成的查询是带有

must

子句的

bool

查询

var firstSearchResponse = client.Search<Project>(s => s
    .Query(q => q
        .Term(p => p.Name, "x") && q
        .Term(p => p.Name, "y")
    )
);

并使用对象初始化程序语法

var secondSearchResponse = client.Search<Project>(new SearchRequest<Project>
{
    Query = new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" } &&
            new TermQuery { Field = Field<Project>(p => p.Name), Value = "y" }
});

两者都产生以下JSON查询DSL

{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "name": { "value": "x" } }
        },
        {
          "term": {
            "name": { "value": "y" } }
        }
      ]
    }
  }
}

运算符重载的原生实现将被重写

term && term && term

to

bool
|___must
   |___term
   |___bool
       |___must
           |___term
           |___term

你可以想象查询越复杂,这变得相当笨拙。 NEST足够聪明地加入

&&

查询,形成一个单一的

bool

查询

bool
|___must
   |___term
   |___term
   |___term

如下所示

Assert(
    q => q.Query() && q.Query() && q.Query(), 
    Query && Query && Query, 
    c => c.Bool.Must.Should().HaveCount(3) 
);

一元

!

运算符

NEST还提供了一个简写,使用一元的

!

运算符创建一个带

must_not

子句

bool

查询

var firstSearchResponse = client.Search<Project>(s => s
    .Query(q => !q
        .Term(p => p.Name, "x")
    )
);

并使用对象初始化程序语法

var secondSearchResponse = client.Search<Project>(new SearchRequest<Project>
{
    Query = !new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" }
});

两者都产生以下JSON查询DSL

{
  "query": {
    "bool": {
      "must_not": [
        {
          "term": {
            "name": { "value": "x" } }
        }
      ]
    }
  }
}

两个查询标记有一元

!

运算符可以与

&&

运算符组合,形成一个带有两个

must_not

子句的

bool

查询

Assert(
    q => !q.Query() && !q.Query(), 
    !Query && !Query, 
    c => c.Bool.MustNot.Should().HaveCount(2)); 

一元

+

运算符

一个查询可以转换成一个带有

filter

子句的

bool

查询,使用一元

+

运算符

var firstSearchResponse = client.Search<Project>(s => s
    .Query(q => +q
        .Term(p => p.Name, "x")
    )
);

并使用Object Initializer语法

var secondSearchResponse = client.Search<Project>(new SearchRequest<Project>
{
    Query = +new TermQuery { Field = Field<Project>(p => p.Name), Value = "x" }
});

两者都产生以下JSON查询DSL

{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "name": { "value": "x" } }
        }
      ]
    }
  }
}

这将在过滤器上下文中运行查询,这可以在不需要查询的相关性分数来影响结果顺序的情况下提高性能。

与一元

!

运算符一样,用一元

+

运算符标记的查询可以与

&&

运算符组合,形成一个带有两个

filter

条件的

bool

查询

Assert(
    q => +q.Query() && +q.Query(),
    +Query && +Query,
    c => c.Bool.Filter.Should().HaveCount(2));

组合布尔查询

当将多个查询与二进制

&&

运算符组合时,其中一些或所有查询具有一元运算符应用,NEST仍然可以组合它们以形成单个

bool

查询。

以下列

bool

查询为例

bool
|___must
|   |___term
|   |___term
|   |___term
|
|___must_not
   |___term

这可以用NEST构建

Assert(
    q => q.Query() && q.Query() && q.Query() && !q.Query(),
    Query && Query && Query && !Query,
    c=>
    {
        c.Bool.Must.Should().HaveCount(3);
        c.Bool.MustNot.Should().HaveCount(1);
    });

一个更复杂的例子

term && term && term && !term && +term && +term

仍然只导致一个单一的

bool

查询具有以下结构

bool
|___must
|   |___term
|   |___term
|   |___term
|
|___must_not
|   |___term
|
|___filter
   |___term
   |___term
Assert(
    q => q.Query() && q.Query() && q.Query() && !q.Query() && +q.Query() && +q.Query(),
    Query && Query && Query && !Query && +Query && +Query,
    c =>
    {
        c.Bool.Must.Should().HaveCount(3);
        c.Bool.MustNot.Should().HaveCount(1);
        c.Bool.Filter.Should().HaveCount(2);
    });

您仍然可以将实际的

bool

查询与运算符重载查询进行混合和匹配,例如

bool(must=term, term, term) && !term

这仍然会合并成一个单一的

bool

查询

Assert(
    q => q.Bool(b => b.Must(mq => mq.Query(), mq => mq.Query(), mq => mq.Query())) && !q.Query(),
    new BoolQuery { Must = new QueryContainer[] { Query, Query, Query } } && !Query,
    c =>
    {
        c.Bool.Must.Should().HaveCount(3);
        c.Bool.MustNot.Should().HaveCount(1);
    });

将查询与

||

或should 子句 组合

根据上一个例子,NEST将组合多个

should



||

进入一个单独的

bool

查询用

should

子句,当它看到

bool

查询只包含should子句;

总而言之,这个

term || term || term
bool
|___should
   |___term
   |___term
   |___term

但是,

bool

查询并不完全符合您期望的编程语言的相同布尔逻辑。 那是

term1 && (term2 || term3 || term4)

不会变成

bool
|___must
|   |___term1
|
|___should
   |___term2
   |___term3
   |___term4

为什么是这样? 那么,当一个

bool

查询只有

should

子句,其中至少有一个必须匹配。 但是,当该

bool

查询也有一个

must

子句时,

should

语句现在作为一个增强因子,这意味着没有一个必须匹配,但是如果这样做,那么该文档的相关性得分将被提升,因此在结果。 基于

must

子句的存在,应该如何应用语句的语义改变。

所以,把这回到前面的例子,你可以得到只包含term1的结果。 这显然不是使用操作符重载时的意图。

为了帮助这一点,NEST将以前的查询重写为

bool
|___must
   |___term1
   |___bool
       |___should
           |___term2
           |___term3
           |___term4
Assert(
    q => q.Query() && (q.Query() || q.Query() || q.Query()),
    Query && (Query || Query || Query),
    c =>
    {
        c.Bool.Must.Should().HaveCount(2);
        var lastMustClause = (IQueryContainer)c.Bool.Must.Last();
        lastMustClause.Should().NotBeNull();
        lastMustClause.Bool.Should().NotBeNull();
        lastMustClause.Bool.Should.Should().HaveCount(3);
    });

在构建搜索查询时,使用

should

子句作为增强因子可能是一个非常强大的构造,并且记住,您可以将实际的

bool

查询与NEST的运算符重载进行混合和匹配。

还有一个微妙的情况,NEST不会盲目地合并两个

bool

查询,只有

should

子句。 请考虑以下几点

bool(should=term1, term2, term3, term4, minimum_should_match=2) || term5 || term6

如果NEST确定二进制的两边

||

操作只包含

should

子句并将它们加在一起,它将给第一个

bool

查询的

minimum_should_match

参数带来不同的含义; 重写这个到一个

bool

与5个

should

子句将打破原始查询的语义,因为只有匹配

term5



term6

仍然是一个命中。

Assert(
    q => q.Bool(b => b
        .Should(mq => mq.Query(), mq => mq.Query(), mq => mq.Query(), mq => mq.Query())
        .MinimumShouldMatch(2)
        )
         || !q.Query() || q.Query(),
    new BoolQuery
    {
        Should = new QueryContainer[] { Query, Query, Query, Query },
        MinimumShouldMatch = 2
    } || !Query || Query,
    c =>
    {
        c.Bool.Should.Should().HaveCount(3);
        var nestedBool = c.Bool.Should.First() as IQueryContainer;
        nestedBool.Bool.Should.Should().HaveCount(4);
    });

锁定的布尔查询

如果设置了任何查询元数据,则NEST不会组合

bool

查询,例如设置了诸如

boost



name

等元数据,NEST将把它们视为已锁定。

这里我们演示两个锁定的

bool

查询未组合

Assert(
    q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query()))
         || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())),
    new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } }
    || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } },
    c => AssertDoesNotJoinOntoLockedBool(c, "leftBool"));

两个

bool

查询都不是正确的查询被锁定的

Assert(
    q => q.Bool(b => b.Should(mq => mq.Query()))
         || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())),
    new BoolQuery { Should = new QueryContainer[] { Query } }
    || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } },
    c => AssertDoesNotJoinOntoLockedBool(c, "rightBool"));

或左侧查询被锁定

Assert(
    q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query()))
         || q.Bool(b => b.Should(mq => mq.Query())),
    new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } }
    || new BoolQuery { Should = new QueryContainer[] { Query } },
    c => AssertDoesNotJoinOntoLockedBool(c, "leftBool"));

性能考虑

如果您有使用bool dsl组合许多查询的要求,请考虑以下内容。

您可以在循环中使用按位分配将许多查询组合成更大的布尔。

在这个例子中,我们使用

&=

分配操作符创建一个包含1000个must子句的bool查询。

var c = new QueryContainer();
var q = new TermQuery { Field = "x", Value = "x" };

for (var i = 0; i < 1000; i++)
{
    c &= q;
}
|     Median|     StdDev|       Gen 0|  Gen 1|  Gen 2|  Bytes Allocated/Op
|  1.8507 ms|  0.1878 ms|    1,793.00|  21.00|      -|        1.872.672,28

正如您可以看到,仍然很快,它的原因是大量的分配发生,因为每次迭代,我们需要重新评估我们的bool查询的可合并性。

由于我们早已知道我们的布尔查询的形状,所以要做到这一点要快得多:

QueryContainer q = new TermQuery { Field = "x", Value = "x" };
var x = Enumerable.Range(0, 1000).Select(f => q).ToArray();
var boolQuery = new BoolQuery
{
    Must = x
};
|      Median|     StdDev|   Gen 0|  Gen 1|  Gen 2|  Bytes Allocated/Op
|  31.4610 us|  0.9495 us|  439.00|      -|      -|            7.912,95

如果您在NEST 2.4.6之前将许多布尔查询分配到使用赋值循环的较大的布尔查询中,则客户端不会以最优的方式对结果进行平坦化,并且在执行〜2000次迭代时可能会导致堆栈溢出。 这仅适用于按位分配许多布尔查询,其他查询不受影响。

自从NEST 2.4.6以来,您可以结合尽可能多的布尔查询,您也可以这样做


选择要返回的字段

有时您不需要从搜索查询返回文档的所有字段;例如,当在博客上显示最新的帖子时,您可能只需要从查找最近发布的帖子中返回博客的标题。

您可以使用两种方法来仅返回文档中的一些字段,即部分文档(我们在此宽松地使用此术语);使用存储的字段和源过滤。两者在工作方式上有很大的不同。

存储字段

索引文档时,默认情况下,Elasticsearch将原始发送的JSON文档存储在名为_source的特殊字段中。从搜索查询返回的文档从每个命中的弹性搜索返回的_source字段中实现。

还可以通过使用存储在映射上的方式,将单独的JSON文档中的字段存储在Elasticsearch中。你为什么要这样做?那么你可以禁用

_source

,以便源不被存储,并选择仅存储特定的字段。另一种可能性是

_source

包含一个具有较大值的字段,例如博客文章的正文,但通常只需要另一个字段,例如博客文章的标题。在这种情况下,我们不想浪费Elasticsearch反序列化整个_soure的,只是为了获得一个小的字段。

选择禁用类型映射的

_source

意味着发送到Elasticsearch的原始JSON文档不存储,因此永远不会被检索。 虽然您可以节省磁盘空间,但某些功能在禁用

_source

(例如Reindex API或即时高亮显示)时将无法正常工作。

认真考虑是否禁用

_source

是您真正想为您的用例做的事情。

以这种方式存储字段时,可以使用搜索请求中的

.StoredFields

来指定要返回的各个字段值

var searchResponse = client.Search<Project>(s => s
    .StoredFields(sf => sf
        .Fields(
            f => f.Name,
            f => f.StartedOn,
            f => f.Branches
        )
    )
    .Query(q => q
        .MatchAll()
    )
);

并且可以使用

.Fields

对响应进行检索

foreach (var fieldValues in searchResponse.Fields)
{
    var document = new 
    {
        Name = fieldValues.ValueOf<Project, string>(p => p.Name),
        StartedOn = fieldValues.Value<DateTime>(Infer.Field<Project>(p => p.StartedOn)),
        Branches = fieldValues.Values<Project, string>(p => p.Branches.First())
    };
}

从所请求的存储字段构造部分文档

这可以分开存储字段。 然而,更常见的情况是仅从

_source

返回一些字段; 这是来源过滤的地方

源过滤

只能使用源过滤从搜索查询返回文档的某些字段

var searchResponse = client.Search<Project>(s => s
    .Source(sf => sf
        .Includes(i => i 
            .Fields(
                f => f.Name,
                f => f.StartedOn,
                f => f.Branches
            )
        )
        .Excludes(e => e 
            .Fields("num*") 
        )
    )
    .Query(q => q
        .MatchAll()
    )
);

在请求中指定了源过滤器后,

.Documents

现在将包含从指定包含的源字段实现的部分文档

var partialProjects = searchResponse.Documents;

可以排除

_source

从一个查询中完全返回

searchResponse = client.Search<Project>(s => s
    .Source(false)
    .Query(q => q
        .MatchAll()
    )
);

协变检索结果

NEST直接支持返回的协变结果集。 意思是结果可以键入到接口或基类,但实际的实例类型的结果可以是直接的子类

我们来看一个例子 想象一下,我们要搜索所有实现ISearchResult的多种类型

public interface ISearchResult
{
    string Name { get; set; }
}

我们有三个实现的ISearchResult,即

A



B



C

public class A : ISearchResult
{
    public string Name { get; set; }
    public int PropertyOnA { get; set; }
}

public class B : ISearchResult
{
    public string Name { get; set; }
    public int PropertyOnB { get; set; }
}

public class C : ISearchResult
{
    public string Name { get; set; }
    public int PropertyOnC { get; set; }
}

使用类型

搜索多种类型的最简单的方法是键入对父界面或基类的响应,并使用

.Type()

传递要搜索的实际类型。

var result = this._client.Search<ISearchResult>(s => s
    .Type(Types.Type(typeof(A), typeof(B), typeof(C)))
    .Size(100)
);

NEST将把它翻译成

/index/a,b,c/_search

; 具有

"_type" : "a"

的命中将被序列化为

A

等等

在这里,我们假设我们的响应是有效的,我们收到了我们期待的100个文件。 记住

result.Documents

是一个

IReadOnlyCollection<ISearchResult>

result.ShouldBeValid();
result.Documents.Count.Should().Be(100);

为了证明返回的结果集是协变的,我们根据其实际类型过滤文档,并声明返回的子集是预期的大小

var aDocuments = result.Documents.OfType<A>();
var bDocuments = result.Documents.OfType<B>();
var cDocuments = result.Documents.OfType<C>();

aDocuments.Count().Should().Be(25);
bDocuments.Count().Should().Be(25);
cDocuments.Count().Should().Be(50);

并且假定仅在子类本身上存在的属性被正确地填充

aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0);
bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0);
cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);

使用ConcreteTypeSelector

更低级的方法是自己检查命中并确定要反序列化的CLR类型

var result = this._client.Search<ISearchResult>(s => s
    .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C))
    .Size(100)
);

这里为每个命中我们将调用传递给

ConcreteTypeSelector

的委托

 `d`是暴露为`dynamic `类型的`_source`的表示形式
 一个类型的`h`表示源的封装命中,即`Hit<dynamic>`

在这里,我们假设我们的响应是有效的,我们收到了我们期待的100个文件。 记住

result.Documents

是一个

IReadOnlyCollection<ISearchResult>

result.ShouldBeValid();
result.Documents.Count.Should().Be(100);

为了证明返回的结果集是协变的,我们根据其实际类型过滤文档,并声明返回的子集是预期的大小

var aDocuments = result.Documents.OfType<A>();
var bDocuments = result.Documents.OfType<B>();
var cDocuments = result.Documents.OfType<C>();

aDocuments.Count().Should().Be(25);
bDocuments.Count().Should().Be(25);
cDocuments.Count().Should().Be(50);

并且假定仅在子类本身上存在的属性被正确地填充

aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0);
bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0);
cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);

使用CovariantTypes

Scroll API是上一个搜索示例的延续,因此

Types()

将丢失。 您可以使用

.CovariantTypes()

提示类型

var result = this._client.Scroll<ISearchResult>(TimeSpan.FromMinutes(60), "scrollId", s => s
    .CovariantTypes(Types.Type(typeof(A), typeof(B), typeof(C)))
);

NEST将把它翻译成

/index/a,b,c/_search

; 具有

"_type" : "a"

的命中将被序列化为

A

等等

在这里,我们假设我们的响应是有效的,我们收到了我们期待的100个文件。 记住

result.Documents

是一个

IReadOnlyCollection<ISearchResult>

result.ShouldBeValid();
result.Documents.Count.Should().Be(100);

为了证明返回的结果集是协变的,我们根据其实际类型过滤文档,并声明返回的子集是预期的大小

var aDocuments = result.Documents.OfType<A>();
var bDocuments = result.Documents.OfType<B>();
var cDocuments = result.Documents.OfType<C>();

aDocuments.Count().Should().Be(25);
bDocuments.Count().Should().Be(25);
cDocuments.Count().Should().Be(50);

并且假定仅在子类本身上存在的属性被正确地填充

aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0);
bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0);
cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);

也可以在滚动上指定较低级别的具体类型选择器

var result = this._client.Scroll<ISearchResult>(TimeSpan.FromMinutes(1), "scrollid", s => s
    .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C))
);

像以前一样,在委托中传递给

.ConcreteTypeSelector

 `d`是类型为`dynamic`的`_source`
 `h`是封装类型的命中

在这里,我们假设我们的响应是有效的,我们收到了我们期待的100个文件。 记住

result.Documents

是一个

IReadOnlyCollection<ISearchResult>

result.ShouldBeValid();
result.Documents.Count.Should().Be(100);

为了证明返回的结果集是协变的,我们根据其实际类型过滤文档,并声明返回的子集是预期的大小

var aDocuments = result.Documents.OfType<A>();
var bDocuments = result.Documents.OfType<B>();
var cDocuments = result.Documents.OfType<C>();

aDocuments.Count().Should().Be(25);
bDocuments.Count().Should().Be(25);
cDocuments.Count().Should().Be(50);

并且假定仅在子类本身上存在的属性被正确地填充

aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0);
bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0);
cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);