I found a solution based on Your hints and a StackOverflow question. My solution is unfortunately Nest only (but I think it's not a big deal to get the rests out).
const string ELASTIC_SEARCH_SERVER_URI = @"http://localhost:9200";
const string INDEX_NAME = "my_projects";
const string DEAFULT_INDEX_NAME = "something_index";
var uri = new Uri(ELASTIC_SEARCH_SERVER_URI);
var settings = new ConnectionSettings(uri)
.DefaultIndex(DEAFULT_INDEX_NAME)
.InferMappingFor<Project>(d => d
.IndexName(USERS_INDEX_NAME)
);
var client = new ElasticClient(settings);
client.CreateIndex(INDEX_NAME, descriptor => descriptor
.Mappings(ms => ms
.Map<Project>(m => m.AutoMap())
)
.Settings(s => s
.Analysis(a => a
.Analyzers(analyzer => analyzer
.Custom("substring_analyzer", analyzerDescriptor => analyzerDescriptor
.Tokenizer("keyword")
.Filters("lowercase", "substring")
)
)
.TokenFilters(tf => tf
.NGram("substring", filterDescriptor => filterDescriptor
.MinGram(1)
.MaxGram(9)
)
)
)
)
);
ISearchResponse<Project> result = _client.Search<Project>(request => request
.From(0).Size(100)
.Query(q => q.Term(p => p.ExternalCode, "LE"))
.PostFilter(bm => bm.Bool(b => b.Must(GetTerms(query)))));
var foundProjects = result.Documents;
And the term building method:
private Func<QueryContainerDescriptor<Project>, QueryContainer>[] GetTerms(ProjectsQuery query)
{
var terms = new List<Func<QueryContainerDescriptor<Examination>, QueryContainer>>();
if (query.IsOpen != null)
{
terms.Add(bm => bm.Term(p => p.IsOpen, query.IsOpen));
}
if (query.IsDeployed != null)
{
terms.Add(bm => bm.Term(p => p.IsDeployed, query.IsDeployed));
}
if (!terms.Any())
{
terms.Add(bm => bm.MatchAll());
}
return terms.ToArray();
}
And the model:
[ElasticsearchType(Name = "project")]
public class Project
{
public Guid Id { get; set; }
public bool IsOpen { get; set; }
public bool IsDeployed { get; set; }
[Text(Analyzer = "substring_analyzer")]
public string ExternalCode { get; set; }
public Owner Owner { get; set; }
}
public class Owner
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
It works, but I'm not sure if the filters are being created properly. The last question is - how to make the Term search also through Owner.FirstName and Owner.LastName?
UPDATE: This is my current solution for multiple-fields term search
ISearchResponse<Project> result = _client.Search<Project>(request => request
.From(0).Size(5).Query(q => q
.Bool(b => b
.Should(
s => s.Term(p => p.ExternalCode, query.SearchPhrase),
s => s.Term(p => p.Owner.FirstName, query.SearchPhrase),
s => s.Term(p => p.Owner.LastName, query.SearchPhrase)
)
))
.PostFilter(bm => bm.Bool(b => b.Must(GetTerms(query)))));
I modified the Owner class by adding the same [Text(Analyzer="substring_analyzer)] attribute over the FirstName and LastName. Now the problem is, how to make it possible to search both - the name and the surname. It works as a "search-while-type", but when I type the whole name and the beginning of the last name, it breaks.