using Lucene.Net.Analysis; using Lucene.Net.Documents; using Lucene.Net.Store; using Masuit.LuceneEFCore.SearchEngine.Extensions; using Masuit.LuceneEFCore.SearchEngine.Interfaces; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; namespace Masuit.LuceneEFCore.SearchEngine { /// /// 搜索引擎 /// /// public class SearchEngine : ISearchEngine where TContext : DbContext { /// /// 数据库上下文 /// public TContext Context { get; } /// /// 索引器 /// public ILuceneIndexer LuceneIndexer { get; } /// /// 索引搜索器 /// public ILuceneIndexSearcher LuceneIndexSearcher { get; } /// /// 索引条数 /// public int IndexCount => LuceneIndexer.Count(); /// /// 搜索引擎 /// /// 数据库上下文 /// /// /// public SearchEngine(TContext context, Directory directory, Analyzer analyzer, IMemoryCache memoryCache) { Context = context; LuceneIndexer = new LuceneIndexer(directory, analyzer); LuceneIndexSearcher = new LuceneIndexSearcher(directory, analyzer, memoryCache); } /// /// 检查数据库上下文更改,并返回LuceneIndexChanges类型的集合 /// /// LuceneIndexChangeset - 转换为LuceneIndexChanges类型的实体更改集合 private LuceneIndexChangeset GetChangeset() { var changes = new LuceneIndexChangeset(); foreach (var entity in Context.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged)) { var entityType = entity.Entity.GetType(); if (!typeof(ILuceneIndexable).IsAssignableFrom(entityType) || entityType.GetMethod("ToDocument") is null) { continue; } var change = new LuceneIndexChange(entity.Entity as ILuceneIndexable); switch (entity.State) { case EntityState.Added: change.State = LuceneIndexState.Added; break; case EntityState.Deleted: change.State = LuceneIndexState.Removed; break; case EntityState.Modified: change.State = LuceneIndexState.Updated; break; default: change.State = LuceneIndexState.Unchanged; break; } changes.Entries.Add(change); } return changes; } /// ///获取文档的具体版本 /// /// 要转换的文档 /// private ILuceneIndexable GetConcreteFromDocument(Document doc) { var t = Type.GetType(doc.Get("Type")); var obj = Expression.Lambda>(Expression.New(t.GetConstructors()[0])).Compile()(); foreach (var p in t.GetProperties().Where(p => p.GetCustomAttributes().Any())) { p.SetValue(obj, doc.Get(p.Name, p.PropertyType)); } return obj; } /// /// 保存数据更改并同步索引 /// /// public int SaveChanges(bool index = true) { int result = 0; if (Context.ChangeTracker.HasChanges()) { // 获取要变更的实体集 var changes = GetChangeset(); result = Context.SaveChanges(); if (changes.HasChanges && index) { LuceneIndexer.Update(changes); } } return result; } /// /// 保存数据更改并同步索引 /// /// 是否需要被重新索引 /// public async Task SaveChangesAsync(bool index = true) { int result = 0; if (Context.ChangeTracker.HasChanges()) { // 获取要变更的结果集 var changes = GetChangeset(); result = await Context.SaveChangesAsync(); if (changes.HasChanges && index) { LuceneIndexer.Update(changes); } } return result; } /// /// 扫描数据库上下文并对所有已实现ILuceneIndexable的对象,并创建索引 /// public void CreateIndex() { if (LuceneIndexer == null) { return; } var index = new List(); var properties = Context.GetType().GetProperties(); foreach (var pi in properties) { if (typeof(IEnumerable).IsAssignableFrom(pi.PropertyType)) { var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null); index.AddRange(entities as IEnumerable); } } if (index.Any()) { LuceneIndexer.CreateIndex(index); } } /// /// 创建指定数据表的索引 /// public void CreateIndex(List tables) { if (LuceneIndexer == null) { return; } var index = new List(); var properties = Context.GetType().GetProperties(); foreach (var pi in properties) { if (typeof(IEnumerable).IsAssignableFrom(pi.PropertyType) && tables.Contains(pi.Name)) { var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null); index.AddRange(entities as IEnumerable); } } if (index.Any()) { LuceneIndexer.CreateIndex(index); } } /// /// 删除索引 /// public void DeleteIndex() { LuceneIndexer?.DeleteAll(); } /// /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型,但不返回任何评分信息 /// /// 要搜索的实体类型 - 注意:必须实现ILuceneIndexable /// 搜索选项 /// public ISearchResultCollection Search(SearchOptions options) { options.Type = typeof(T); var indexResults = LuceneIndexSearcher.ScoredSearch(options); ISearchResultCollection resultSet = new SearchResultCollection { TotalHits = indexResults.TotalHits }; var sw = Stopwatch.StartNew(); foreach (var indexResult in indexResults.Results) { var entity = (T)GetConcreteFromDocument(indexResult.Document); resultSet.Results.Add(entity); } sw.Stop(); resultSet.Elapsed = indexResults.Elapsed + sw.ElapsedMilliseconds; return resultSet; } /// /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型,但不返回任何评分信息 /// /// 要搜索的实体类型 - 注意:必须实现ILuceneIndexable /// 搜索选项 /// public IScoredSearchResultCollection ScoredSearch(SearchOptions options) { // 确保类型匹配 if (typeof(T) != typeof(ILuceneIndexable)) { options.Type = typeof(T); } var indexResults = LuceneIndexSearcher.ScoredSearch(options); IScoredSearchResultCollection results = new ScoredSearchResultCollection(); results.TotalHits = indexResults.TotalHits; var sw = Stopwatch.StartNew(); foreach (var indexResult in indexResults.Results) { IScoredSearchResult result = new ScoredSearchResult(); result.Score = indexResult.Score; result.Entity = (T)GetConcreteFromDocument(indexResult.Document); results.Results.Add(result); } sw.Stop(); results.Elapsed = indexResults.Elapsed + sw.ElapsedMilliseconds; return results; } /// /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型 /// /// 搜索选项 /// public IScoredSearchResultCollection ScoredSearch(SearchOptions options) { return ScoredSearch(options); } /// /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型 /// /// 搜索选项 /// public ISearchResultCollection Search(SearchOptions options) { return Search(options); } /// /// 搜索一条匹配度最高的记录 /// /// 搜索选项 /// public ILuceneIndexable SearchOne(SearchOptions options) { return GetConcreteFromDocument(LuceneIndexSearcher.ScoredSearchSingle(options)); } /// /// 搜索一条匹配度最高的记录 /// /// 搜索选项 /// public T SearchOne(SearchOptions options) where T : class { return GetConcreteFromDocument(LuceneIndexSearcher.ScoredSearchSingle(options)) as T; } } }