How to get synonyms working in elastic search through C#? - c#

For the last few hours I've been trying to get the synonyms working in Elastic Search but so far failed.
When I do the search it's like the synonyms never get indexed.
Below is the part of code where I think I may be making a mistake.
Please have a look and any help will be much appreciated.
var createIndexTask = Client.CreateIndexAsync(synonymsindexname, i => i
.Settings(s => s
.Analysis(a => a
.TokenFilters(tf => tf
.Synonym("my_synonym", sf => sf
.Format(SynonymFormat.Solr)
.SynonymsPath("synonyms/solrsynonyms.txt")
)
)
.Analyzers(an => an
.Custom("my_analyzer", ca => ca
.Tokenizer("standard")
.Filters("lowercase", "stop", "my_synonym")
)
)
)
)
.Mappings(m => m
.Map<ClinicalCode>(c => c
.AutoMap()
.Properties(p => p
.Completion(comp => comp
.Contexts(ctx => ctx
.Category(csug => csug
.Name("ShortDescription")
.Path(x => x.ShortDescription)
)
)
.Name(n => n.Suggests)
.Analyzer("simple")
)
.Text(t => t
.Name(n => n.LongDescription)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Text(t => t
.Name(n => n.ShortDescription)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Text(t => t
.Name(n => n.ICDCode)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Keyword(k => k
.Name(n => n.ICDCode)
)
)
)
)
);

Related

Exclude nulls from ThenInclude

I have EF context as below
_context.Offices
.Where( x => x.ID == id )
.Include( x => x.County)
.ThenInclude( x => x.Employees)
.ThenInclude( s => s.Department)
.ThenInclude( m => m.DeptUser )
.Include( x => x.XYZ);
My problem is that DeptUser can be null. How do I exclude null items?

Elasticsearch / Nest search using MultiMatch with default boosting for all other fields

I'm trying to build a query that basically searches over all full text fields, boosting a few, but leaving all others at the default boost of 1.
When I don't include any fields, everything has a boost of 1 (we're on version 6.4.2 which supports default when no fields are specified):
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Query(request.Query)
)
)
);
However, as soon as I try to boost a single field, it removes the defaults on all the other fields, only searching on the explicit field:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
);
I tried adding a wildcard, but this still just matches on firstName (then again, the wildcard on its own doesn't match anything, so assuming I have the syntax wrong on that):
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f.Field("*.*^1"))
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
);
I also tried Booling them together, but this also just matches on firstName:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.Bool(b => b
.Should(m => m
.MultiMatch(mm => mm
.Query(request.Query)
)
)
.Should(m => m
.MultiMatch(mm => mm
.Fields(f => f.Field("firstName^20"))
.Query(request.Query)
)
)
)
)
);
I'm starting to think this isn't possible. For context, the reason I'm trying to do this is to be able to add other full text fields to the index without having to include every field in our queries, but still be able to boost certain fields.
Figured out my problem. I was chaining multiple .Fields() (plural) together, where I should only have a single .Fields() (plural) and then chain multiple .Field() (singular) together:
var results = await _ElasticClient.SearchAsync<dynamic>(s => s
.Query(q => q
.MultiMatch(m => m
.Fields(f => f
.Field("firstName^20")
.Field("*.*^1")
)
.Query(request.Query)
)
)
);

Dynamic Query in Elastic NEST

So I have manually built the following Query in NEST which works correctly,
What im trying to figure out is a way to build this dynamically dependent on what Product Specification Filters are passed in
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)))
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Guarantee")
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("3 years")
)))
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Brand")
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("Sony")
)))
After looking at the link that Russ has shared I think the solution is to pass in my filters
public class ElasticJsonFilter
{
public string name { get; set; }
public List<string> Values { get; set; } = new List<string>();
}
Which will give me names such as "Guarantee" and values such as "2 Years, 3 Years" looking at the code that Russ linked I can get his to run through my list of filters and spit out boolean filters but I guess I need to get this to build a nested query?
elasticParams.Filters.Aggregate(new QueryContainer(), (c, s) => c && +q.Term(p => p.Name, s), c => c);

Elastic NEST Filter on Aggregations

Ive now successfully got my c# Test win forms app working with aggregations and now I want to be able to filter on said aggregations if someone selects one (or more) of them.
Here is my query that is working exactly as I want and I am getting both price range buckets and buckets for each of the specification terms that it can find in the index.
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa.Terms("groups", sp => sp.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt.Field(ff => ff.ProductSpecification.Suffix("value"))))
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
))
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)))));
Can someone point me in the direction of how I can go about filtering down results based on selecting any of these buckets.
UPDATE (30/01/18) I have now added this to my query
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Guarantee",)
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("3 years","10 years")
)))
));
This is enabling me to pass multiple values into one Spec filter, but what im not sure on how to achieve is how to filter on multiple specs so the above filters on Guarantee with values of either 3 or 10 years
but if I also wanted to pass values of "Grey" and "Copper" to the spec of "Colour"
Adding "Colour" into My first set of terms and "Grey" and "Copper" into my second terms list breaks all filtering.
I think im close here just need a little direction
If I understand your question correctly, when the user selects a price range from the price range facets that are applicable to the query that is being executed, you'd like to apply this price range to the query?
The Post Filter serves this purpose, by applying a filter to the search results after aggregations have been calculated.
Assuming a user has selected the 50-100 price range, your query would like something like
var response = client.Search<Product>(s => s
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa
.Terms("groups", sp => sp
.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt
.Field(ff => ff.ProductSpecification.Suffix("value"))
)
)
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
)
)
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)
)
)
)
.PostFilter(pf => pf
.Range(r => r
.Field(f => f.Price)
.GreaterThanOrEquals(50)
.LessThan(100)
)
)
);

How do I specify an index for Elasticsearch using NEST?

If I run the code below it will create a mapping on ALL indices, which I don't want. I am unable to find the documentation for specifying just the index I want.
How do I specify which index to apply this mapping to?
var client = new ElasticClient();
var response = client.Map<Company>(m => m
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
);
Add .Index() to the put mapping descriptor
var response = client.Map<Company>(m => m
.Index("index-name")
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
);
This puts a mapping into an existing index. If an index doesn't yet exist, you can create it and define a mapping for it in one request. For example
var createIndexResponse = client.CreateIndex("index-name", c => c
// settings for the index
.Settings(s => s
.NumberOfShards(3)
.NumberOfReplicas(1)
.RefreshInterval("5s")
)
// mappings for the index
.Mappings(m => m
.Map<Company>(mc => mc
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
)
)
);
Something like this also will do.
string IndexName = "my_index";
this.client.CreateIndex(IndexName, c =>
c.AddMapping<CForm>
(m => m.Properties(ps => ps.Attachment
(a => a.Name(o => o.Document)
.TitleField(t => t.Name(x => x.Name)
.TermVector(TermVectorOption.WithPositionsOffsets))))));
// Create Mappings for the fields with specific properties.
// You can also make field1 a multi-field and make it both analyzed and not_analyzed
// to get the best of both worlds (i.e. text matching on the analyzed field + aggregation on the exact value
// of the not_analyzed raw sub-field).
// Field: Plan
var result = this.client.Map<CForm>(m => m
.Properties(props => props
.MultiField(s => s
.Name(p => p.Plan)
.Fields(pprops => pprops
.String(ps => ps.Name(p => p.Plan).Index(FieldIndexOption.NotAnalyzed))
.String(ps => ps.Name("original").Index(FieldIndexOption.Analyzed))
)
)
)
);

Categories