- .NET Clients: other versions:
- Introduction
- Breaking changes
- API Conventions
- Elasticsearch.Net - Low level client
- NEST - High level client
- Troubleshooting
- Search
- Query DSL
- Full text queries
- Term level queries
- Exists Query Usage
- Fuzzy Date Query Usage
- Fuzzy Numeric Query Usage
- Fuzzy Query Usage
- Ids Query Usage
- Prefix Query Usage
- Date Range Query Usage
- Numeric Range Query Usage
- Term Range Query Usage
- Regexp Query Usage
- Term Query Usage
- Terms List Query Usage
- Terms Lookup Query Usage
- Terms Query Usage
- Type Query Usage
- Wildcard Query Usage
- Compound queries
- Joining queries
- Geo queries
- Geo Bounding Box Query Usage
- Geo Distance Query Usage
- Geo Distance Range Query Usage
- Geo Hash Cell Query Usage
- Geo Polygon Query Usage
- Geo Shape Circle Query Usage
- Geo Shape Envelope Query Usage
- Geo Shape Geometry Collection Query Usage
- Geo Shape Indexed Shape Query Usage
- Geo Shape Line String Query Usage
- Geo Shape Multi Line String Query Usage
- Geo Shape Multi Point Query Usage
- Geo Shape Multi Polygon Query Usage
- Geo Shape Point Query Usage
- Geo Shape Polygon Query Usage
- Specialized queries
- Span queries
- NEST specific queries
- Aggregations
- Metric Aggregations
- Average Aggregation Usage
- Cardinality Aggregation Usage
- Extended Stats Aggregation Usage
- Geo Bounds Aggregation Usage
- Geo Centroid Aggregation Usage
- Max Aggregation Usage
- Min Aggregation Usage
- Percentile Ranks Aggregation Usage
- Percentiles Aggregation Usage
- Scripted Metric Aggregation Usage
- Stats Aggregation Usage
- Sum Aggregation Usage
- Top Hits Aggregation Usage
- Value Count Aggregation Usage
- Bucket Aggregations
- Adjacency Matrix Usage
- Children Aggregation Usage
- Date Histogram Aggregation Usage
- Date Range Aggregation Usage
- Filter Aggregation Usage
- Filters Aggregation Usage
- Geo Distance Aggregation Usage
- Geo Hash Grid Aggregation Usage
- Global Aggregation Usage
- Histogram Aggregation Usage
- Ip Range Aggregation Usage
- Missing Aggregation Usage
- Nested Aggregation Usage
- Range Aggregation Usage
- Reverse Nested Aggregation Usage
- Sampler Aggregation Usage
- Significant Terms Aggregation Usage
- Terms Aggregation Usage
- Pipeline Aggregations
- Average Bucket Aggregation Usage
- Bucket Script Aggregation Usage
- Bucket Selector Aggregation Usage
- Cumulative Sum Aggregation Usage
- Derivative Aggregation Usage
- Extended Stats Bucket Aggregation Usage
- Max Bucket Aggregation Usage
- Min Bucket Aggregation Usage
- Moving Average Ewma Aggregation Usage
- Moving Average Holt Linear Aggregation Usage
- Moving Average Holt Winters Aggregation Usage
- Moving Average Linear Aggregation Usage
- Moving Average Simple Aggregation Usage
- Percentiles Bucket Aggregation Usage
- Serial Differencing Aggregation Usage
- Stats Bucket Aggregation Usage
- Sum Bucket Aggregation Usage
- Matrix Aggregations
- Metric Aggregations
WARNING: Version 5.x has passed its EOL date.
This documentation is no longer being maintained and may be removed. If you are running this version, we strongly advise you to upgrade. For the latest information, see the current release documentation.
Covariant search results
editCovariant search results
editNEST directly supports returning covariant result sets. Meaning a result can be typed to an interface or base class but the actual instance type of the result can be that of the subclass directly
Let’s look at an example; Imagine we want to search over multiple types that all implement
ISearchResult
public interface ISearchResult { string Name { get; set; } } public abstract class BaseX : ISearchResult { public string Name { get; set; } }
We have three implementations of ISearchResult
namely A
, B
and C
public class A : BaseX { public int PropertyOnA { get; set; } } public class B : BaseX { public int PropertyOnB { get; set; } } public class C : BaseX { public int PropertyOnC { get; set; } }
Using types
editThe most straightforward way to search over multiple types is to
type the response to the parent interface or base class
and pass the actual types we want to search over using .Type()
var result = this._client.Search<ISearchResult>(s => s .Type(Types.Type(typeof(A), typeof(B), typeof(C))) .Size(100) );
NEST will translate this to a search over /index/a,b,c/_search
;
hits that have "_type" : "a"
will be serialized to A
and so forth
Here we assume our response is valid and that we received the 100 documents
we are expecting. Remember result.Documents
is an IReadOnlyCollection<ISearchResult>
result.ShouldBeValid(); result.Documents.Count.Should().Be(100);
To prove the returned result set is covariant we filter the documents based on their actual type and assert the returned subsets are the expected sizes
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);
and assume that properties that only exist on the subclass itself are properly filled
aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);
Using ConcreteTypeSelector
editA more low level approach is to inspect the hit yourself and determine the CLR type to deserialize to
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) );
here for each hit we’ll call the delegate passed to ConcreteTypeSelector
where
-
d
is a representation of the_source
exposed as adynamic
type -
a typed
h
which represents the encapsulating hit of the source i.e.Hit<dynamic>
Here we assume our response is valid and that we received the 100 documents
we are expecting. Remember result.Documents
is an IReadOnlyCollection<ISearchResult>
result.ShouldBeValid(); result.Documents.Count.Should().Be(100);
To prove the returned result set is covariant we filter the documents based on their actual type and assert the returned subsets are the expected sizes
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);
and assume that properties that only exist on the subclass itself are properly filled
aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);
Using CovariantTypes
editThe Scroll API is a continuation of the previous Search example so Types() are lost.
You can hint at the types using .CovariantTypes()
var result = this._client.Scroll<ISearchResult>(TimeSpan.FromMinutes(60), "scrollId", s => s .CovariantTypes(Types.Type(typeof(A), typeof(B), typeof(C))) );
NEST will translate this to a search over /index/a,b,c/_search
;
hits that have "_type" : "a"
will be serialized to A
and so forth
Here we assume our response is valid and that we received the 100 documents
we are expecting. Remember result.Documents
is an IReadOnlyCollection<ISearchResult>
result.ShouldBeValid(); result.Documents.Count.Should().Be(100);
To prove the returned result set is covariant we filter the documents based on their actual type and assert the returned subsets are the expected sizes
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);
and assume that properties that only exist on the subclass itself are properly filled
aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);
The more low level concrete type selector can also be specified on scroll
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)) );
As before, within the delegate passed to .ConcreteTypeSelector
-
d
is the_source
typed asdynamic
-
h
is the encapsulating typed hit
Here we assume our response is valid and that we received the 100 documents
we are expecting. Remember result.Documents
is an IReadOnlyCollection<ISearchResult>
result.ShouldBeValid(); result.Documents.Count.Should().Be(100);
To prove the returned result set is covariant we filter the documents based on their actual type and assert the returned subsets are the expected sizes
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);
and assume that properties that only exist on the subclass itself are properly filled
aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0);
Covariance also works over subclasses not just interfaces
var result = this._client.Search<BaseX>(s => s .Type(Types.Type(typeof(A), typeof(B), typeof(C))) .Size(100) ); 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);