I am using Elasticsearch v6 and NEST to search the advertisements that I create on my website.
This is my Document (from which I build the Index
):
[ElasticsearchType(Name = "document")]
public class Document
{
public long Id { get; set; }
[Text(Analyzer = "english")]
public string Title { get; set; }
public int Price { get; set; }
public short AdDurationInDays { get; set; }
public DateTime AdStartTime { get; set; }
[Text(Analyzer = "english")]
public string Description { get; set; }
[Text(Index = false)] // Don't want to query this field
public string MainPhoto { get; set; }
public int ParentCategoryId { get; set; }
[Text(Analyzer = "english")]
public string ParentCategoryName { get; set; }
public GeoLocation GeoLocation { get; set; }
}
I have a created Elasticsearch client and I have a method called SearchDocuments
. I want to pass a search term (keyword) and a list of Filters to this method. Search document needs to build a dynamic query based on the search term and filters which are passed in.
This is my search method:
public void SearchDocuments(KeywordMultiMatch Keyword, List<IQueryBuilder> filters)
{
QueryContainer multiMatchQuery = Keyword.GetQuery();
QueryContainer filterQuery = null;
foreach (var filter in filters)
{
var query = filter.GetQuery();
if (query != null)
{
filterQuery &= query;
}
}
var res = Client.Search<Document>(new SearchRequest<Document>
{
Query = new BoolQuery
{
Must = new QueryContainer[] { multiMatchQuery },
Filter = new QueryContainer[] { filterQuery }
}
});
}
In order to build my dynamic search query, I have created an interface called IQueryBuilder
. This interface has one method called GetQuery()
, which returns a QueryContainer
.
public interface IQueryBuilder
{
QueryContainer GetQuery();
}
Now my search term class (Keyword) and all the filters implement this interface, this is the KeywordMultiMatch
class which returns a MultiMatchQuery
:
public class KeywordMultiMatch : IQueryBuilder
{
[StringLength(100, MinimumLength = 2)]
public string keyword { get; set; }
public QueryContainer GetQuery()
{
if (!string.IsNullOrEmpty(keyword))
{
var query = new MultiMatchQuery
{
Name = "Keyword Multimatch",
Fields = Fields<Document>(p => p.Title).And<Document>(p => p.Description).And<Document>(p => p.ParentCategoryName).And<Document>(p => p.ParentCategoryName),
Query = keyword,
Fuzziness = Fuzziness.EditDistance(1)
};
return query;
}
return new MatchAllQuery();
}
}
This is my LocationFilter
class:
public class LocationFilter : IQueryBuilder
{
public short Distance { get; set; }
public GeoLocation GeoLocation { get; set; }
public QueryContainer GetQuery()
{
if (GeoLocation != null && Distance > 0)
{
var query = new GeoDistanceQuery
{
Name = "Location Filter",
Field = Field<Document>(p => p.GeoLocation),
DistanceType = GeoDistanceType.Plane, // plane is faster but less accurate than arc
Location = GeoLocation,
Distance = Distance.ToString() + "km"
};
return query;
}
return null;
}
}
And this is my ParentCategoryFilter
class:
public class ParentCategoryFilter : IQueryBuilder
{
public int ParentCategoryId { get; set; }
public QueryContainer GetQuery()
{
if (ParentCategoryId > 0)
{
var query = new TermQuery
{
Name = "Parent Category Filter",
Field = Field<Document>(p => p.ParentCategoryId),
Value = ParentCategoryId
};
return query;
}
return null;
}
}