I am having trouble to find information about registering the different building blocks of Lucene into the dependency injection. I am also not sure about the lifetimes.
I think one should reuse the IndexWriter as it seems to be costly. And i managed to do so.
But when it comes to search im lost. Should i have DI handle the IndexReader or the LuceneDictionary as well as the AnalyzingInfixSuggester ? And how could this be done.
This is how i went for now:
Registration:
public static IServiceCollection AddLucene(this IServiceCollection services)
{
var version = Lucene.Net.Util.LuceneVersion.LUCENE_48;
var directory = new RAMDirectory();
var analyzer = new StandardAnalyzer(version);
var config = new IndexWriterConfig(version, analyzer);
var writer = new IndexWriter(directory, config);
services.AddSingleton(writer);
services.AddSingleton(analyzer);
services.AddSingleton(directory);
services.AddTransient<SearchService>();
services.AddTransient<IndexService>();
return services;
}
IndexWriter:
public IndexService(IndexWriter writer)
{
_writer = writer;
}
public void WriteIndex()
{
var searchResults = QueryDatabase();
var idField = new StringField(nameof(SearchResult.DatabaseId), "", Field.Store.YES);
var headField = new StringField(nameof(SearchResult.Header), "", Field.Store.YES);
var bodyField = new TextField(nameof(SearchResult.Body), "", Field.Store.YES);
var typeField = new TextField(nameof(SearchResult.Type), "", Field.Store.YES);
var doc = new Document
{
idField,
headField,
bodyField,
typeField
};
foreach (var result in searchResults)
{
idField.SetStringValue(result.DatabaseId);
headField.SetStringValue(result.Header);
bodyField.SetStringValue(result.Body);
typeField.SetStringValue(result.Type);
_writer.AddDocument(doc);
}
_writer.Commit();
}
Search:
public SearchService(StandardAnalyzer analyzer, RAMDirectory directory)
{
_analyzer = analyzer;
_directory = directory;
}
public SearchResponse Search(string input, int page)
{
var reader = DirectoryReader.Open(_directory);
var searcher = new IndexSearcher(reader);
var multiFieldQP = new MultiFieldQueryParser(LuceneVersion.LUCENE_48, _searchFields, _analyzer);
var _input = EscapeSearchTerm(input.Trim());
var query = multiFieldQP.Parse(_input);
var docs = searcher.Search(query, null, 1000).ScoreDocs;
...
}
public List<string> SearchAhead(string input)
{
var reader = DirectoryReader.Open(_directory);
var dictionary = new LuceneDictionary(reader, nameof(SearchResult.Header));
using var analyzingSuggester =
new AnalyzingInfixSuggester(LuceneVersion.LUCENE_48, new RAMDirectory(), _analyzer);
analyzingSuggester.Build(dictionary);
var lookupResultList = analyzingSuggester.DoLookup(input.Trim(), false, 9);
...
}
Most of this code is based on https://beckshome.com/2022/11/lucene-blazor-part-3-auto-complete and i modified it to work with asp.net core api.
It is an interesting question but for me I only use dependency injection if I have multiple types of the dependency that I might chose to use and it's important to be able to swap out those types system wide easily.
So that is to say I didn’t personally put any aspects of Lucene in my DI container.
You could of course registering the IndexWriter
as a singleton if you are using NRT (Near REAL TIME) readers.
But that only makes sense if you are using Lucene.NET to manage a single index. Otherwise you'd probably need to wrap that IndexWriter
in a custom type that helps you know which index it's a writer for. Then that custom type could be registered in DI as a singleton and provide static access to the associated IndexWriter
.