Статьи

Как добавить возможность поиска в .Net приложения, используя Lucene.net

Одним из ключевых факторов при определении того, является ли программное приложение пригодным для использования, является то, могут ли пользователи легко перемещаться по нему, в то же время находя соответствующую информацию и ресурсы посредством предоставления функциональности поиска. Несмотря на то, что библиотеки поиска Google или Bing легко интегрировать в программные приложения, иногда необходимо использовать библиотеку, которая предоставляет широкие возможности поиска и в то же время предоставляет разработчикам аналитику поиска. Есть много проприетарных поисковых библиотек или структур, которые попадают в эти категории, такие как Excite. Но есть открытая и надежная библиотека поиска, которая показалась мне очень интересной, когда мне нужно добавить возможности поиска в приложения .net;  Lucene.net

Lucene.net — это .net-порт Lucene, полнотекстового поискового движка с открытым исходным кодом, написанного на Java. Lucene.net при включении в приложения .Net обеспечивает полнотекстовый поиск. Кроме того, Lucene.Net может использоваться для индексации объектов каркаса сущностей для облегчения поиска благодаря проекту LINQ to Lucene. Исходный код ofLucene.Net может быть получен из  https://github.com/apache/lucene.net  и компиляции или скомпилированные двоичные файлы из самородка  Install-Package Lucene.Net 

В этом блоге, я быстро продемонстрировать , как Lucene. Net можно использовать в приложении .Net для полнотекстового поиска. Прежде чем мы начнем писать коды, некоторые понятия и термины Lucene должны быть объяснены и прояснены. 

поле: это имя, словарь пары значений или хеш-таблица, имя может быть заголовком или темой сообщения в блоге, а значением будет текст. Значения поля могут быть сохранены, проиндексированы и проанализированы. 

Документ : это последовательность полей 

Каталог : здесь Lucene хранит индексы и может находиться в папке в системе или в памяти. 

IndexWriter : это компонент Lucene, который создает и оптимизирует индексы, а также добавляет документы в индексы. 

Анализатор : это компонент Lucene, который извлекает индексные ключи или термины из текста. 

IndexReader: это компонент Lucene, который позволяет легко искать и извлекать проиндексированные поля. Я продемонстрирую мощь Lucene.net, используя его для индексирования страниц приложения ASP.net MVC. Во-первых, двоичные файлы Lucene.net должны быть добавлены в приложение MVC через слепок. Затем необходимо создать папку внутри папки App_Data, я назвал папку LuceneIndexes, когда это будет сделано, следующая вещь — это инициализация каталога и индексатора, которые будет использовать Lucene.net, и это лучше всего сделать во время запуска приложения, добавив Класс LuceneSearchConfig в папку App_Start с кодами, как показано ниже. 

[assembly: WebActivator.PostApplicationStartMethod(typeof(Blog.Web.App_Start.LuceneSearchConfig),
 "InitializeSearch")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(Blog.Web.App_Start.
LuceneSearchConfig),"FinalizeSearch")]

namespace Blog.Web.App_Start
{
    using Blog.Domain.Service;
    using Lucene.Net.Analysis;
    using Lucene.Net.Analysis.Standard;
    using Lucene.Net.Documents;
    using Lucene.Net.Index;
    using Lucene.Net.QueryParsers;
    using Lucene.Net.Search;
    using Lucene.Net.Store;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Web;

    public class LuceneSearchConfig
    {
        public static Directory directory;
        public static Analyzer analyzer;
        public static IndexWriter writer;

        public static void InitializeSearch()
        {
            string directoryPath = AppDomain.CurrentDomain.BaseDirectory + @"\App_Data\LuceneIndexes";
            directory = FSDirectory.Open(directoryPath);
            analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
            writer = new IndexWriter(directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
            Task.Factory.StartNew(() => CreateIndex());
        }

        private static void CreateIndex()
        {

            IBlogService blogService = new BlogService(repository);
            var pages = blogService.GetAllPages();
            Document doc;
            string pageUrl = string.Empty;
            string pagePath = string.Empty;
            foreach (var page in pages)
            {
                doc = new Document();
                pageUrl = string.Concat("~/Blog/", page.PageViewName);
                pagePath = string.Concat(AppDomain.CurrentDomain.BaseDirectory, @"\Views\MyBlog\", page.PageViewName, ".cshtml");
                doc.Add(new Field("postUrl", pageUrl, Field.Store.YES, Field.Index.NOT_ANALYZED));
                doc.Add(new Field("postTitle", page.PageTitle, Field.Store.YES, Field.Index.NOT_ANALYZED));
                doc.Add(new Field("postBody", pagePath, Field.Store.YES, Field.Index.ANALYZED));
                writer.AddDocument(doc);
                doc = null;
            }
            writer.Optimize();
            writer.Commit();
            writer.Dispose();
        }

        public static void FinalizeSearch()
        {
            directory.Dispose();
        }
    }
}

Let me break down the codes and explain it. The lines of codes below initializes the folder where the Lucene.net Indexes would be stored while at the same time instantiation of Analyzer and IndexWriter were done. 

string directoryPath = AppDomain.CurrentDomain.BaseDirectory + @"\App_Data\LuceneIndexes";
directory = FSDirectory.Open(directoryPath);
analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
writer = new IndexWriter(directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED);

The fields to be indexed would be added to the Document object, before adding the object to the index writer as shown in the codes below. 

doc = new Document();
pageUrl = string.Concat("~/Blog/", page.PageViewName);
pagePath = string.Concat(AppDomain.CurrentDomain.BaseDirectory,@"\Views\MyBlog\", page.PageViewName, ".cshtml");
doc.Add(new Field("postUrl", pageUrl, Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.Add(new Field("postTitle", page.PageTitle, Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.Add(new Field("postBody", pagePath, Field.Store.YES, Field.Index.ANALYZED));
writer.AddDocument(doc);

After the indexes have been added to the document, the Optimize method of the IndexWriter is called to optimize the indexes for faster access, before calling the Commit and Dispose methods, which would save the indexes to the folder and close the IndexWriter. 

writer.Optimize();
writer.Commit();
writer.Dispose();

Creating the query and making the search is relatively easy, once the indexes have been created. The LuceneIndexes directory needs to be opened once again, using the FSDirectory object which would then be passed into the constructor of IndexSearcher object. A Parser object would be created which would pass the search word or query before the Search method on the IndexSearcher is called. A POCO class was created to hold the results of the search which would in turn be passed to the view for further processing. The code is as shown below 

public ActionResult Search(FormCollection formCollection)
        {
              List <SearchResult> searchResults = new List <SearchResult>();
                var query = formCollection["search"];
                string indexDirectory = Server.MapPath("~/App_Data/LuceneIndexes");
                var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30);
                IndexSearcher searcher = new IndexSearcher(FSDirectory.Open(indexDirectory));
                var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_30, "postBody", analyzer);
                Query searchQuery = parser.Parse(query);
                TopDocs hits = searcher.Search(searchQuery, 200);
                int results = hits.TotalHits;
                SearchResult searchResult = null;
                for (int i = 0; i < results; i++)
                {
                    Document doc = searcher.Doc(hits.ScoreDocs[i].Doc);
                    searchResult = new SearchResult();
                    searchResult.PostUrl = doc.Get("postUrl");
                    searchResult.PostTitle = doc.Get("postTitle");
                    searchResult.PostIntro = doc.Get("postIntro");
                    searchResults.Add(searchResult);
                    searchResult = null;
               }
              return View(searchResults);
        }

    public class SearchResult
    {
        public string PostUrl { get; set; }

        public string PostTitle { get; set; }

        public string PostIntro { get; set; }
    }

In this blog post, I have demonstrated how Lucene.net can be quickly used in an ASP.net MVC application to provide a full text search.