Exact search with ElasticSearch using EdgeNGram - c#

I am using elastic for searching with greek and latin characters.My main problem is that I can't do exact searches.I am using edgeNgram filter on indexing, but I would like to control its min and max at search time according to my word length.For example if I type "titanox" I will firstly get "ΤΙΤΑΝΙΟΥ" and secondly "TITANOX".Here is my index creation :
var response = client.CreateIndex(index, s => s
.Settings(s1 => s1
.NumberOfShards(5)
.NumberOfReplicas(5)
.Analysis(a => a.TokenFilters(t => t.IcuTransform("greeklatin", it => it.Id("Greek-Latin; NFD; [:Nonspacing Mark:] Remove; NFC")//
.Direction(IcuTransformDirection.Forward)) //
.IcuTransform("latingreek", lg => lg.Id("Greek-Latin; NFD; [:Nonspacing Mark:] Remove; NFC")
.Direction(IcuTransformDirection.Reverse))
.EdgeNGram("greekedge", ed => ed.MaxGram(7)
.MinGram(1)
.Side(EdgeNGramSide.Front))
.Stop("greekstop", sw => sw.StopWords())
.Lowercase("greeklowercase", gl => gl.Language(Language.Greek.ToString()))
.KeywordMarker("greekkeywords", gk => gk.Keywords(""))
.Stemmer("greekstemmer", gs => gs.Language(Language.Greek.ToString())))
.Analyzers(a1 => a1
.Custom("greek", t => t.Tokenizer("standard")
.Filters("greekedge", "greekstop", "greeklowercase", "greekkeywords", "greekstemmer", "greeklatin")))))
.Mappings(m => m.Map(type, mt => mt.Properties(c => c.Text(c1 => c1.Name("id").Analyzer("greek"))
.Text(c2 => c2.Name("brand").Analyzer("greek"))
.Text(c3 => c3.Name("service").Analyzer("greek"))
.Text(c4 => c4.Name("servicegroupdesc").Analyzer("greek"))
.Text(c5 => c5.Name("servicecategorydesc).Analyzer("greek"))
.Text(c6 => c6.Name("partscategory").Analyzer("greek"))
.Text(c7 => c7.Name("partsid").Analyzer("greek"))
.Text(c8 => c8.Name("partsdesc").Analyzer("greek"))))));
and here my search
var response = client.Search<Cars>(n => n
.Index(index)
.Type(type)
.Query(m => m.MultiMatch(q => q
.Analyzer(analyzername)
//.MinimumShouldMatch("100%")
.Query("*" + searchWord + "*")
.Fields(f=>f.Field(fieldsForSearchList[0]))
.Fuzziness(Fuzziness.EditDistance(0))))
.Size(searchSize)
.From(0)
.TrackScores(true)
);

A solution that may can help is adding to this query a new query for boosting the word that users type.This can achieve more exact searches.

Related

How to take top 3 elements in each group using Entity Framework

I have the data in the source table.
And I want to get this kind of result in list using lambda expression and Entity Framework. I don't know how to do it. I need to get top 3 rows in for each CategoryId.
Probably using something like this:
context.GroupBy(x => x.CategoryId)
.Select(group => new {
CategoryId = group.Key,
NameId = group.Select(x => x.NameId),
group.OrderByDescending(e => e.Count).Take(3)
})
.ToListAsync()
var list = context
.GroupBy(x => x.CategoryId)
.SelectMany(group => group.OrderByDescending(e => e.Count).Take(3))
.OrderByDescending(e => e.Count)
.ToListAsync();
If you want an anonymous type:
var list = context
.GroupBy(x => x.CategoryId)
.SelectMany(group => group.OrderByDescending(e => e.Count).Take(3))
.Select(x => new
{
x.CategoryId,
x.NameId,
x.Count
})
.OrderByDescending(x => x.Count)
.ToListAsync();

How do you get mixed results when searching multiple types with ElasticSearch 2.x using NEST?

I'm pretty new to Elastic Search and stumbled upon this issue.
When searching multiple document types from the same index, the types are getting appended in the result documents set instead of being default sorted by boosted field.
My document types shares the same fields.
This is my search query:
var response = await client.SearchAsync<ProductListResponse>(s => s
.Type("product,productbundle")
.Index(index)
.From(from)
.Size(size)
.Query(fsq => fsq
.FunctionScore(c => c.Query(q => q
.MultiMatch(m => m.Query(request.Query)
.Fields(f => f
.Field(n => n.Name, 100.0)
.Field(n => n.NameWithoutSpecialChars, 100.0)
.Field(n => n.ProductName)
.Field(n => n.TeaserText)
.Field(n => n.Description)
.Field(n => n.Features)
.Field(n => n.Modules)
)
.Type(TextQueryType.PhrasePrefix)
)
).Functions(f => f
.FieldValueFactor(b => b
.Field(p => p.IsBoosted)
.Modifier(FieldValueFactorModifier.Log1P))
)
)
)
.Sort(ss => ss
.Descending(SortSpecialField.Score))
.PostFilter(filter => filter.Bool(b => b.Must(must => allFilters)))
.Source(sr => sr
.Include(fi => fi
.Field(f => f.Name)
.Field(n => n.ProductName)
.Field(n => n.TeaserText)
.Field(f => f.Image)
.Field(f => f.Thumbnail)
.Field(f => f.Url)
.Field(f => f.Features)
)
)
);
Any help is appreciated.
I would preferre not to adapt the product type with the additons to productbundle type..
I can confirm that .Type() does not mess with the order.
My issue was a boosted property not getting a value while indexing the bundles.

C# elastic search exact text match with nest

I am using latest c# elastic search NEST library.
I am trying to search with exact text match, but currently it is working searching
for subset match. I want to do exact match.
Following is my code snippet:
public User GetUserByUsername(string username)
{
var client = new ElasticConnectionManager(this.configuration).GetClient(Constant.IndexUsers);
var searchResponse = client.Search<User>(s => s
.Query(q => q
.Bool(bq => bq
.Filter(f => f.Term(t => t.Username, username))
.Must(mt=>mt.Term(t2=> t2.Username, username)))));
//.Must(bs => bs.Term(t => t.Username, username))
if (searchResponse.Documents.Count > 0)
return searchResponse.Documents.First();
else
return null;
}
}
Try using the match_phrase query for exact text match. Your query should be similar to the following:
var searchResponse = client.Search<User>(s => s
.Query(q => q
.MatchPhrase(m => m
.Field(f => f.Username)
.Query(username))));

Aggregation by phrase in ElasticSearch using C# Nest

This is my aggregation:
return a =>
a.Terms("Group1", t => t.Field(ff => ff.ItemAttributeDesc1).
Aggregations(aa => aa.Terms("Value1", tt => tt.Field(fff => fff.SearchedFilterValue1))))
.Terms("Group2", t => t.Field(ff => ff.ItemAttributeDesc2).
Aggregations(aa => aa.Terms("Value2", tt => tt.Field(fff => fff.SearchedFilterValue2))))
.Terms("Group3", t => t.Field(ff => ff.ItemAttributeDesc3).
Aggregations(aa => aa.Terms("Value3", tt => tt.Field(fff => fff.SearchedFilterValue3))))
.Terms("Group4", t => t.Field(ff => ff.ItemAttributeDesc4).
Aggregations(aa => aa.Terms("Value4", tt => tt.Field(fff => fff.SearchedFilterValue4))))
.Terms("Group5", t => t.Field(ff => ff.ItemAttributeDesc5).
Aggregations(aa => aa.Terms("Value5", tt => tt.Field(fff => fff.SearchedFilterValue5))))
.Terms("Group6", t => t.Field(ff => ff.ItemAttributeDesc6).
Aggregations(aa => aa.Terms("Value6", tt => tt.Field(fff => fff.SearchedFilterValue6))))
.Terms("Group7", t => t.Field(ff => ff.ItemAttributeDesc7).
Aggregations(aa => aa.Terms("Value7", tt => tt.Field(fff => fff.SearchedFilterValue7))))
.Terms("Group8", t => t.Field(ff => ff.ItemAttributeDesc8).
Aggregations(aa => aa.Terms("Value8", tt => tt.Field(fff => fff.SearchedFilterValue8))))
.Terms("Brands", t => t.Field(ff => ff.VendorSearch).Size(60).Order(TermsOrder.TermAscending))
.Terms("Category", t => t.Field(ff => ff.MainSearch))
.Range("Price", ra => ra.Field(ff => ff.SalePrice)
.Ranges(
pr => pr.From(50).To(100),
pr => pr.From(100).To(250),
pr => pr.From(250).To(500),
pr => pr.From(500).To(750),
pr => pr.From(750).To(1000),
pr => pr.From(1000).To(1500),
pr => pr.From(1500).To(2000),
pr => pr.From(2000).To(2500),
pr => pr.From(2500).To(3000),
pr => pr.From(3000).To(3500),
pr => pr.From(3500)));
It's working as expected except one thing. I have to aggregate by full match. For example, if I have term field "Field" (one word) it's working fine. But when if the term contains of several words (or have specific symbols) it's not working. Not working, because elastic split phrase by spaces and symbols. I need 100% match here. Could anyone help me to resolve the issue?
This happens because the field you are aggregating on is mapped as an analyzed string which is the default mapping for string types, to resolve this, map the relevant fields you are aggregating on as type: string and index: not_analyzed
See more information regarding analyzed strings

How to partially project a child object with many fields in nHibernate

I have the following nHibernate query that select a course based on its course id and then return selected fields for the course object on the initial fetch, and the query executes with no issues.
MatchMode option = ...
CourseItem courseAlias = null;
TeacherItem teacherAlias = null;
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
.Select(c => c.Teacher).WithAlias(() => courseAlias.Teacher))
.TransformUsing(Transformers.AliasToBean<CourseItem>())
.List<CourseItem>();
I wanted to go a step further with the query to only return a partial teacher object, let's say i just wanted to return the ID and Name. So, I updated the projected list to as follows:
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
.Select(c => c.Teacher.ID).WithAlias(() => courseAlias.Teacher.ID)
.Select(c => c.Teacher.Name).WithAlias(() => courseAlias.Teacher.Name))
.TransformUsing(Transformers.AliasToBean<CourseItem>())
.List<CourseItem>();
The query doesn't work because nHibernate has no idea how to resovled based on Teacher.ID and Teacher.Name. Any thoughts on whether it's possible to NOT fetch the entire child object back to a parent object?
I've tried the following query and it works this is not my fully desired outcome
var query = session.QueryOver<CourseItem>(() => courseAlias)
.JoinAlias(() => courseAlias.Teacher, () => teacherAlias)
.Where(() => courseAlias.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(() => courseAlias.CourseID)
.Select(() => courseAlias.IsActive)
.Select(() => courseAlias.CourseDesc)
.Select(() => teacher.ID)
.Select(() => teacher.Name))
.List<object[]>();
I can query the right values but unable to transform it back correctly to the Course / teacher data type.
Any thoughts?
thanks!
We can indeed use custom transformer. There is one, which I am using for a really very very deep projections (inlcuding dynamic objects - 5.1.13. component, dynamic-component)
DeepTransformer<TEntity>
Take it (if needed adjust it) and your final query could be like this
// just the last lines are different
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
// the native WitAlias would not work, it uses expression
// to extract just the last property
//.Select(c => c.Teacher.ID).WithAlias(() => courseAlias.Teacher.ID)
//.Select(c => c.Teacher.Name).WithAlias(() => courseAlias.Teacher.Name))
// so we can use this way to pass the deep alias
.Select(Projections.Property(() => teacherAlias.ID).As("Teacher.ID"))
.Select(Projections.Property(() => teacherAlias.Name).As("Teacher.Name"))
// instead of this
// .TransformUsing(Transformers.AliasToBean<CourseItem>())
// use this
.TransformUsing(new DeepTransformer<CourseItem>())
And in case, that your aliases do match to property names, that transformer will built the object tree...

Categories

Resources