Полнотекстовый поисковый движок на основе EntityFrameworkCore и Lucene.NET (библиотека Curl)
Объём кода всего 70 КБ! Подходит для новичков. Полнотекстовый поисковик, основанный на EntityFrameworkCore и Lucene.NET, позволяет легко реализовать высокопроизводительный полнотекстовый поиск, поддерживает добавление пользовательских словарей, пользовательских синонимов и омофонов, по умолчанию поддерживает поиск по омофонам при разбиении на слова. Может быть легко применён к любой объектно-реляционной базе данных на основе EntityFrameworkCore.
Примечание: этот проект подходит только для простых сценариев поиска в монолитных приложениях, не подходит для распределённых приложений и сложных сценариев поиска. Для распределённых приложений рекомендуется использовать большие поисковые системы, такие как ElasticSearch, или рассмотреть возможность использования регулярных выражений в базах данных
Официальный сайт | Примеры реального применения
Режим разработки проекта: ежедневное накопление кода + сбор данных из сети
Если компания, использующая этот открытый исходный код или содержащая код этого проекта, будет признана виновной в нарушении трудового законодательства (включая, но не ограничиваясь незаконным увольнением, сверхурочной работой, использованием детского труда и т. д.) в любом судебном процессе, автор этого проекта имеет право требовать плату за использование этого проекта (в размере от 2 до 5 раз суммы регистрационного сбора компании). В противном случае использование любого исходного кода, содержащего этот проект, будет запрещено! Аутсорсинговые компании
или 007 компании
должны получить коммерческое разрешение от автора, чтобы использовать эту библиотеку. Другие предприятия или частные лица могут свободно использовать её без ограничений. 007 — это компания по найму людей, которая также их эксплуатирует. Восемь часов работы позволяют вам иметь время для самосовершенствования, что делает вас конкурентоспособным в будущем. Против 007, каждый несёт ответственность!
⭐⭐⭐ Если вам нравится этот проект, пожалуйста, поставьте звёздочку, разветвите его и подпишитесь на обновления! ⭐⭐⭐
Потому что этот проект вводит несколько связанных с Lucene библиотек. Если он будет интегрирован в Masuit.Tools, это неизбежно увеличит количество ссылок на пакеты существующих проектов, и может быть, что некоторые проекты не используют Lucene. Это приведёт к раздуванию проекта. Поэтому был создан новый проект.
ES действительно хорош, но я думаю, что есть ещё много небольших сайтов, которым не нужно использовать такой тяжёлый промежуточный слой, поэтому выбор оригинального Lucene Library является хорошим выбором. Однако изучение оригинального API Lucene относительно сложно, поэтому была создана эта библиотека.
Создайте новый проект и установите соответствующие библиотеки EntityFrameworkCore и полнотекстового поиска:
В зависимости от вашей ситуации выберите соответствующую версию суффикса, предоставляя четыре версии библиотеки с разными основными ключами. Суффикс int представляет основной ключ на основе типа int с автоинкрементом, суффикс Guid представляет основной ключ на основе типа Guid...
PM> Install-Package Masuit.LuceneEFCore.SearchEngine_int
PM> Install-Package Masuit.LuceneEFCore.SearchEngine_long
PM> Install-Package Masuit.LuceneEFCore.SearchEngine_string
PM> Install-Package Masuit.LuceneEFCore.SearchEngine_Guid
Следуя шаблону, мы должны сначала создать каркас EntityFrameworkCore, то есть контекст базы данных и объекты сущности;
Подготовьте контекст базы данных:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options){}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll);
}
public virtual DbSet<Post> Post { get; set; }
}
Подготовить объект сущности, здесь следует отметить, что для того, чтобы данные этого проекта могли быть полнотекстово проиндексированы, необходимо выполнить два условия:
/// <summary>
/// Статья
/// </summary>
[Table("Post")]
public class Post : LuceneIndexableBaseEntity
{
public Post()
{
PostDate = DateTime.Now;
}
/// <summary>
/// Заголовок
/// </summary>
[Required(ErrorMessage = "Заголовок статьи не может быть пустым!"), LuceneIndex]
public string Title { get; set; }
/// <summary>
/// Автор
/// </summary>
[Обязательный, MaxLength(24, ErrorMessage = "Имя автора не должно превышать 24 символов!"), LuceneIndex]
общедоступная строка Автор {получить; установить;}
/// **Вот перевод исходного кода на русский язык:**
///
///
///
///
LuceneIndexAttribute соответствует четырём настраиваемым параметрам:
1. Name: имя настраиваемого поля индекса, по умолчанию пусто;
2. Index: действие индекса, по умолчанию Field.Index.ANALYZED;
3. Store: сохраняется ли в индексной библиотеке, по умолчанию Field.Store.YES;
4. IsHtml: является ли html, по умолчанию false, если отмечено как true, то при анализе индекса сначала очистит его от тегов html.
#### Зачем сущности должны наследовать от LuceneIndexableBaseEntity?
LuceneIndexableBaseEntity исходный код выглядит следующим образом:
```csharp
/// <summary>
/// Сущность, которая должна быть проиндексирована
/// </summary>
public abstract class LuceneIndexableBaseEntity : ILuceneIndexable
{
/// <summary>
/// Первичный ключ
/// </summary>
[LuceneIndex(Name = "Id", Store = Field.Store.YES, Index = Field.Index.NOT_ANALYZED), Key]
public int Id { get; set; }
/// <summary>
/// Уникальный индексный идентификатор
/// </summary>
[LuceneIndex(Name = "IndexId", Store = Field.Store.YES, Index = Field.Index.NOT_ANALYZED)]
[NotMapped]
public string IndexId
{
get => GetType().Name + ":" + Id;
set
{
}
}
/// <summary>
/// Преобразование в документ Lucene
/// </summary>
/// <returns></returns>
public virtual Document ToDocument()
{
// Логика преобразования объекта сущности в документ Lucene
}
}
После того как сущность наследует от LuceneIndexableBaseEntity, становится удобно вызывать метод ToDocument для хранения в Lucene, при этом первичный ключ Id и уникальный индексный идентификатор IndexId должны участвовать в уникальном индексе документа Lucene (но IndexId не будет сохранён в базе данных).
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<DataContext>(db =>
{
db.UseSqlServer("Data Source=.;Initial Catalog=MyBlogs;Integrated Security=True");
}); // Настройка контекста базы данных
services.AddSearchEngine<DataContext>(new LuceneIndexerOptions()
{
Path = "lucene"
}); // Зависимость внедрения поисковой системы и настройка пути к индексной библиотеке
// ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISearchEngine searchEngine, LuceneIndexerOptions luceneIndexerOptions)
{
// ...
// Импорт пользовательского словаря, поддержка китайских и английских слов
KeywordsManager.AddWords("面向对象编程语言");
KeywordsManager.AddWords("懒得勤快");
KeywordsManager.AddWords("码数科技");
KeywordsManager.AddWords("Tree New Bee");
KeywordsManager.AddWords("男♂能可贵");
// Импорт синонимов, поддержка китайских и английских слов
KeywordsManager.AddSynonyms("RDM","Redis Desktop Manager");
KeywordsManager.AddSynonyms("RDM","Remote Desktop Manager");
KeywordsManager.AddSynonyms("VS","Visual Studio");
KeywordsManager.AddSynonyms("Visual Studio","宇宙最强IDE");
KeywordsManager.AddSynonyms("VS","Video Studio");
KeywordsManager.AddSynonyms("难能可贵","男♂能可贵");
// Вопрос: В приведённом выше примере конфигурации были добавлены синонимы VS->Visual Studio и Visual Studio->宇宙最强IDE? Тогда при сегментации сможет ли VS найти косвенный синоним «宇宙最强IDE»?
// Ответ: Не сможет, почему не сможет? Поиск синонимов не реализует рекурсивный поиск, почему не реализован рекурсивный поиск? Потому что словарь синонимов полностью неконтролируемый динамический конфигуратор, если реализован рекурсивный поиск, неправильная конфигурация словаря может легко вызвать бесконечный цикл, поэтому, если необходимо, чтобы VS и «宇宙最强IDE» были синонимами, необходимо дополнительно настроить
// Инициализация индексной библиотеки, рекомендуется использовать вместе с запланированными задачами, регулярно обновлять индексную библиотеку
string lucenePath = Path.Combine(env.ContentRootPath, luceneIndexerOptions.Path);
if (!Directory.Exists(lucenePath) || Directory.GetFiles(lucenePath).Length < 1)
{
// Создание индекса
Console.WriteLine("Индексная библиотека не существует, начинается автоматическое создание индексной библиотеки Lucene...");
searchEngine.CreateIndex(new List<string>()
{
nameof(DataContext.Post),
});
var list = searchEngine.Context.Post.Where(i => i.Status != Status.Pended).ToList(); // Удалить данные, которые не нужно индексировать
searchEngine.LuceneIndexer.Delete(list);
Console.WriteLine("Создание индексной библиотеки завершено!");
}
// ...
}
Синонимы поддерживают прямой и обратный поиск, например, после настройки KeywordsManager.AddSynonyms("地大物博","弟大勿勃")
и KeywordsManager.AddSynonyms("弟大勿勃","地大物博")
они эквивалентны, достаточно одной из них.
HomeController.cs
[Route("[controller]/[action]")]
public class HomeController : Controller
{
private readonly ISearchEngine<DataContext> _searchEngine;
private readonly ILuceneIndexer _luceneIndexer;
public
``` **HomeController**
(ISearchEngine<DataContext> searchEngine, ILuceneIndexer luceneIndexer)
{
_searchEngine = searchEngine;
_luceneIndexer = luceneIndexer;
}
/// <summary>
/// Поиск
/// </summary>
/// <param name="s">Ключевое слово</param>
/// <param name="page">Номер страницы</param>
/// <param name="size">Размер страницы</param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Index(string s, int page, int size)
{
//var result = _searchEngine.ScoredSearch<Post>(new SearchOptions(s, page, size, "Title,Content,Email,Author"));
var result = _searchEngine.ScoredSearch<Post>(new SearchOptions(s, page, size, typeof(Post)));
return Ok(result);
}
/// <summary>
/// Создание индекса
/// </summary>
[HttpGet]
public void CreateIndex()
{
//_searchEngine.CreateIndex();//Сканирование всех таблиц данных, создание индексов, соответствующих условиям библиотеки
_searchEngine.CreateIndex(new List<string>() { nameof(Post) });//Создание индекса для указанной таблицы данных
}
/// <summary>
/// Добавление индекса
/// </summary>
[HttpPost]
public void AddIndex(Post p)
{
//Добавление в базу данных и обновление индекса
_searchEngine.Context.Post.Add(p);
_searchEngine.SaveChanges();
//_luceneIndexer.Add(p);//Простое добавление индекса библиотеки
}
/// <summary>
/// Удаление индекса
/// </summary>
[HttpDelete]
public void DeleteIndex(Post post)
{
//Удаление из базы данных и обновление индексной библиотеки
Post p = _searchEngine.Context.Post.Find(post.Id);
_searchEngine.Context.Post.Remove(p);
_searchEngine.SaveChanges();
//_luceneIndexer.Delete(p);// Простое удаление из индексной библиотеки
}
/// <summary>
/// Обновление индексной библиотеки
/// </summary>
/// <param name="post"></param>
[HttpPatch]
public void UpdateIndex(Post post)
{
//Обновление из базы данных и синхронизация индексной библиотеки
Post p = _searchEngine.Context.Post.Find(post.Id);
//update...
_searchEngine.Context.Post.Update(p);
_searchEngine.SaveChanges();
//_luceneIndexer.Update(p);// Простое обновление индексной библиотеки
}
#### Об обновлении индекса
Чтобы обновить индекс после выполнения любой операции CRUD, просто вызовите метод SaveChanges() из ISearchEngine, а не из DataContext. Это обновит индекс, а затем автоматически вызовет метод SaveChanges() DataContexts. Если вы вызываете метод SaveChanges() непосредственно из DataContexts, он только сохранит данные в базе данных, но не обновит индексную библиотеку.
#### О результатах поиска
Поиск возвращает IScoredSearchResultCollection<T>, который включает время, затраченное на выполнение поиска, общее количество совпадений и количество объектов в каждом наборе результатов, а также количество совпадений в поиске.
<font color=#f00>Особое внимание: в модульном тестировании используется каталог памяти RAM для индексации и поиска, но это предназначено только для целей тестирования, в реальной производственной среде следует использовать каталог физического диска.</font>
#### Демонстрационный проект
[Нажмите здесь](/WebSearchDemo "demo")
### Рекомендуемые проекты
.NET универсальный фреймворк: [Masuit.Tools](https://github.com/ldqk/Masuit.Tools "Masuit.Tools")
Открытый блог-система: [Masuit.MyBlogs](https://github.com/ldqk/Masuit.MyBlogs "Masuit.MyBlogs")
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )