I have two indices with the following configuration with mappings
var settings = new ConnectionSettings(new Uri("http://localhost:9200/"));
settings
.DefaultMappingFor<ManagementIndex>(m => m
.IndexName("management")
)
.DefaultMappingFor<PropertyIndex>(m => m
.IndexName("apartmentproperty")
);
var client = new ElasticClient(settings);
1) Properties mapping
client.Indices.Create("property", i => i
.Settings(s => s
.NumberOfShards(2)
.NumberOfReplicas(0)
)
.Map<PropertyIndex>(map => map
.AutoMap()
.Properties(p => p
.Nested<PropertyData>(n => n
.Name(c => c.property)
.AutoMap()
.Properties(pp => pp
.Text(c => c
.Name(np => np.city)
.Analyzer("standard")
)
.Text(c => c
.Name(np => np.market)
.Fields(ff => ff
.Text(tt => tt
.Name(np => np.market)
.Analyzer("standard")
)
.Keyword(k => k
.Name("keyword")
.IgnoreAbove(256)
)
)
).Text(c => c
.Name(np => np.name)
.Analyzer("standard")
)
)
)
)
)
);
and
2) Owner
if (client.Indices.Exists("owner").Exists)
client.Indices.Delete("owner");
client.Indices.Create("owner", i => i
.Settings(s => s
.NumberOfShards(2)
.NumberOfReplicas(0)
)
.Map<OwnerIndex>(map => map
.AutoMap()
.Properties(p => p
.Nested<OwnerProp>(n => n
.Name(c => c.owner)
.AutoMap()
.Properties(pp => pp
.Text(c => c
.Name(np => np.market)
.Fields(ff => ff
.Text(tt => tt
.Name(np => np.market)
.Analyzer("standard")
)
.Keyword(k => k
.Name("keyword")
.IgnoreAbove(256)
)
)
).Text(c => c
.Name(np => np.name)
.Analyzer("standard")
)
)
)
)
)
);
with the following POCO definitions
public class PropertyData
{
public string name { get; set; }
public string city { get; set; }
public string market { get; set; }
}
public class PropertyIndex
{
public PropertyData property { get; set; }
}
public class OwnerProp
{
public string name { get; set; }
public string market { get; set; }
}
public class OwnerIndex
{
public OwnerProp owner { get; set; }
}
Trying to do a search through the two indices like so
public async Task<object> SearchPropertiesAsync(string searchQuery, List<string> description, int limit = 25, int skip = 1)
{
var propertyfilters = new List<Func<QueryContainerDescriptor<object>, QueryContainer>>();
var ownerFilters = new List<Func<QueryContainerDescriptor<object>, QueryContainer>>();
if (description.Any())
{
propertyfilters.Add(fq => fq.Terms(t => t.Field("property.market.keyword").Terms(description)));
ownerFilters.Add(fq => fq.Terms(t => t.Field("owner.market.keyword").Terms(description)));
}
var searchResponse = await _elasticClient.SearchAsync<object>(s => s
.Index(Indices.Index(typeof(PropertyIndex)).And(typeof(OwnerIndex)))
.Query(q => (q
.Nested(n => n
.Path(Infer.Field<PropertyIndex>(ff => ff.property))
.Query(nq => nq
.MultiMatch(m => m
.Fields(f => f
.Field(Infer.Field<PropertyIndex>(ff => ff.property.city))
.Field(Infer.Field<PropertyIndex>(ff => ff.property.market))
.Field(Infer.Field<PropertyIndex>(ff => ff.property.name))
)
.Operator(Operator.Or)
.Query(searchQuery)
.Fuzziness(Fuzziness.Auto)
) && +q.Bool(bq => bq.Filter(propertyfilters))
))
) || (q
.Nested(n => n
.Path(Infer.Field<OwnerIndex>(ff => ff.mgmt))
.Query(nq => nq
.MultiMatch(m => m
.Fields(f => f
.Field(Infer.Field<OwnerIndex>(ff => ff.owner.market))
.Field(Infer.Field<OwnerIndex>(ff => ff.owner.name))
)
.Operator(Operator.Or)
.Query(searchQuery)
.Fuzziness(Fuzziness.Auto)
)
&& +q.Bool(bq => bq.Filter(ownerFilters))
))
)
).From((skip - 1) * limit)
.Size(limit)
);
return searchResponse.Documents;
}
calling the SearchPropertiesAsync method returns this error messages (truncated for brevity)
....
"index": "owner",
"caused_by": {
"type": "illegal_state_exception",
"reason": "[nested] failed to find nested object under path [property]"
}
....
"index": "property",
"caused_by": {
"type": "illegal_state_exception",
"reason": "[nested] failed to find nested object under path [owner]"
}
Notice that it looks like its trying to perform a nested search of owner. on property index and a nested search of property. on owner index which doesnt exist.
I feel like this should be a very trivial problem but I have been using ElasticSearch for only 4days now and still very new into it.
Is there something I am doing wrongly or is there something I am missing. Have searched the whole internet to even arrive at the solution I have at the moment.
Note that when you executed the nested query only one index at a time, the code works fine but trying to execute on multiple Indices is where my problem lies. Any help will be highly appreciated.
I am using ElasticSearch 7.3.2 and Nest Client 7.3.0.
I don't mind downgrading to a lower version that works.
Apparently, according to the docs
ignore_unmapped
(Optional, boolean) Indicates whether to ignore an unmapped path and not return any documents instead of an error. Defaults to false.
If false, Elasticsearch returns an error if the path is an unmapped field.
You can use this parameter to query multiple indices that may not contain the field path.
So chaining .IgnoreUnmapped(true) on the query body for each of the nested query solved the problem.
Just in case someone else encounter same problem
Related
I have implemented a Search Query through NEST client and was able to get the records. The code is as follows.
var response = clientProvider.Client.Search<ProjectModel>(s => s
.Index("project_index")
.Type("projects")
.Source(so => so.Excludes(f => f.Field(x => x.FileInfo.FileBase64Data)))
.Size(100)
.Query(q => q
.Bool(b => b
.Should(
m => m.QueryString(qs => qs
.Query(searchOptions.SearchTerm)
.Fields(ff => ff.Fields(fields))
.Fuzziness(Fuzziness.Auto)
),
m => m.MultiMatch(qs => qs
.Query(searchOptions.SearchTerm)
.Type(Nest.TextQueryType.PhrasePrefix)
.Fields(ff => ff.Fields(fields))
)
)
)
)
.Sort(ss => ss.Descending(SortSpecialField.Score))
);
And I am mapping the response to my Project Model as follows.
var project = response.Hits.Select(h =>
{
h.Source._id = h.Id;
h.Source.Score = h.Score;
return h.Source;
}).ToList();
When I am trying to implement the same Search in Async way that is
var response = clientProvider.Client.SearchAsync<ProjectModel>(s => s
.Index("project_index")
.Type("projects")
.Source(so => so.Excludes(f => f.Field(x => x.FileInfo.FileBase64Data)))
.Size(100)
.Query(q => q
.Bool(b => b
.Should(
m => m.QueryString(qs => qs
.Query(searchOptions.SearchTerm)
.Fields(ff => ff.Fields(fields))
.Fuzziness(Fuzziness.Auto)
),
m => m.MultiMatch(qs => qs
.Query(searchOptions.SearchTerm)
.Type(Nest.TextQueryType.PhrasePrefix)
.Fields(ff => ff.Fields(fields))
)
)
)
)
.Sort(ss => ss.Descending(SortSpecialField.Score))
);
I am not getting any errors while executing it. But I am not able to get the response.Hits objects to map it back to my original Project Model.
Thanks In advance
In SearchAsync<T>(), response is Task<ISearchResponse<T>>, so you probably want to await it
Map all documents, found in products index, onto ProductDto type:
var result = await _elasticClient.SearchAsync<ProductDto>(x => x.Index("products").MatchAll());
var documents = result.Documents;
I am trying to do an aggregation on a nested object. The following is my json. The first code sample below successfully returns the productCategory Id. However, I want to return the category id and name in the aggregation. I thought I could try the second code sample below but it doesn't work.
"productCategories": [{
"id":6,
"productId":6,
"categoryId":4,
"category":{
"parentId":2,
"name":"Air Fresheners",
"id":6
}
}]
This one aggregates the productCategory id as the key:
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Id)
)
)
)
)
But I need the category info, and this one doesn't work:
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories.First().Category)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Category.Id)
)
)
)
)
If category is simply mapped as object, then the following will work
var searchResponse = client.Search<Document>(s => s
.Aggregations(aggs => aggs
.Nested("agg-categories", nested => nested
.Path(p => p.ProductCategories)
.Aggregations(r => r
.Terms("agg-category", w => w
.Field(f => f.ProductCategories.First().Category.Id)
)
)
)
)
);
I have
var descriptor = new CreateIndexDescriptor("resources")
.Mappings(ms => ms
.Map<ResourceEntity>(m => m
.AutoMap()
.RoutingField(k => k.Required(true))
.Properties(props => props
.Keyword(s1 => s1.Name(p => p.Id).Norms(false))
.Keyword(k => k.Name(p => p.Type).Norms(false))
.Keyword(k => k.Name(p => p.Location).Norms(false))
)
)
.Map<StreamEntity>(m => m
.AutoMap()
.RoutingField(r => r.Required(true))
.Properties(props => props
.Keyword(s1 => s1.Name(p => p.Name).Norms(false))
)
)
.Map<StreamMessageEntity>(m => m
.AutoMap()
.Parent<StreamEntity>()
.RoutingField(r => r.Required(true))
)
);
but it fails
Invalid NEST response built from a unsuccessful low level call on PUT: /elastic/resources
# Audit trail of this API call:
- [1] BadResponse: Node: https://local.earthml.com:8500/elastic/ Took: 00:00:00.6271828
# ServerError: ServerError: 400Type: illegal_argument_exception Reason: "Mapper for [id] conflicts with existing mapping in other types:
[mapper [id] has different [similarity], mapper [id] is used by multiple types. Set update_all_types to true to update [null_value] across all types.]"
# OriginalException: System.Net.WebException: The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Elasticsearch.Net.HttpConnection.<RequestAsync>d__14`1.MoveNext()
# Request:
<Request stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>
# Response:
<Response stream not captured or already read to completion by serializer. Set DisableDirectStreaming() on ConnectionSettings to force it to be set on the response.>
and here are my base classes.
public abstract class ResourceEntity
{
public string Id { get; set; }
public string Location { get; set; }
public string Type { get; set; }
}
public class NamedResourceEntity : ResourceEntity
{
public string Name { get; set; }
}
public class StreamEntity : NamedResourceEntity
{
}
public class StreamMessageEntity : ResourceEntity
{
}
as of now there are not many extra properties on them, but they will come. Do I have to registere everything or could I simply registere the base class with the index in elastic search.
I want all types in the same index, and the Type Property is a seperater for the different classes (it will be unique for each class and set when created).
I had to explicit map properties for all types
descriptor.Mappings(ms => ms
.Map<ResourceEntity>(m => m
.AutoMap()
.RoutingField(k => k.Required())
.Properties(props => props
.Keyword(s1 => s1.Name(p => p.Id).Norms(false))
.Keyword(k => k.Name(p => p.Type).Norms(false))
.Keyword(k => k.Name(p => p.Location).Norms(false))
)
)
.Map<StreamEntity>(m => m
.AutoMap()
.RoutingField(r => r.Required())
.Properties(props => props
.Keyword(s1 => s1.Name(p => p.Id).Norms(false))
.Keyword(k => k.Name(p => p.Type).Norms(false))
.Keyword(k => k.Name(p => p.Name).Norms(false))
.Keyword(k => k.Name(p => p.Location).Norms(false))
)
)
.Map<StreamMessageEntity>(m => m
.AutoMap()
.Parent<StreamEntity>()
.Properties(props => props
.Keyword(s1 => s1.Name(p => p.Id).Norms(false))
.Keyword(k => k.Name(p => p.Type).Norms(false))
.Keyword(k => k.Name(p => p.Location).Norms(false))
)
.RoutingField(r => r.Required())
).Map<StreamMessageAttachmentEntity>(m => m
.AutoMap()
.Parent<StreamMessageEntity>()
.RoutingField(r => r.Required()))
);
Lets say i have an index that contains documents that represent a Message in a discussion.
that document owns a discussionId property.
(it also has its own ID "that represent MessageId")
now, i need to find all discussionIds that have no documents (messages) that match a query.
for example:
"Find all discussionIds , that have no message that contains the text 'YO YO'"
how can i do that?
the class is similar to this:
public class Message
{
public string Id{get;set}
public string DiscussionId {get;set}
public string Text{get;set}
}
You just need to wrap the query that would find matches for the phrase "YO YO" in a bool query must_not clause.
With NEST
client.Search<Message>(s => s
.Query(q => q
.Bool(b => b
.MustNot(mn => mn
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
)
)
);
which, with operator overloading, can be shortened to
client.Search<Message>(s => s
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
Both produce the query
{
"query": {
"bool": {
"must_not": [
{
"match": {
"text": {
"type": "phrase",
"query": "YO YO"
}
}
}
]
}
}
}
To only return DiscussionId values, you can use source filtering
client.Search<Message>(s => s
.Source(sf => sf
.Includes(f => f
.Field(ff => ff.DiscussionId)
)
)
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
And, if you want to get them all, you can use the scroll API
var searchResponse = client.Search<Message>(s => s
.Scroll("1m")
.Source(sf => sf
.Includes(f => f
.Field(ff => ff.DiscussionId)
)
)
.Query(q => !q
.MatchPhrase(m => m
.Field(f => f.Text)
.Query("YO YO")
)
)
);
// fetch the next batch of documents, using the scroll id returned from
// the previous call. Do this in a loop until no more docs are returned.
searchResponse = client.Scroll<Message>("1m", searchResponse.ScrollId);
This is my class when inserting to ES
public class BasicDoc
{
public string Name { get; set; }
public string Url { get; set; }
}
I managed successfully insert my document to ES using NEST. But I'm having trouble to do a aggregation. My goals is to have something similar to SQL Group By. What I did so far:
var response = elastic.Search<BasicDoc>(s => s
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url)
))
);
I tried to aggregate my document based on BasicDoc.Url. Say I have these in my ES:
/api/call1/v1
/api/call2/v1
/api/call1/v1
When I debug, I my Nest.BucketAggregate will have 4 Items key which is api,call1, call2 and v1. I was expecting only 2 which are /api/call1/v1 and /api/call2/v1. What I'm doing wrong?
You currently have analysis set up on your Url property which means that it will be tokenized by the standard analyzer and terms stored in the inverted index. If you need to be able to search on Uri and also need to aggregate on it, then you may consider mapping it as a multi_field where one field mapping analyzes it and another does not. Here's an example index creation with mapping
client.CreateIndex("index-name", c => c
.Mappings(m => m
.Map<BasicDoc>(mm => mm
.AutoMap()
.Properties(p => p
.String(s => s
.Name(n => n.Url)
.Fields(f => f
.String(ss => ss
.Name("raw")
.NotAnalyzed()
)
)
)
)
)
)
);
When you perform your aggregation, you can now use the Uri raw field
var response = client.Search<BasicDoc>(s => s
.Size(0)
.Aggregations(a => a
.Terms("group_by_url", st => st
.Field(o => o.Url.Suffix("raw"))
)
)
);