SearchEngine.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using Lucene.Net.Analysis;
  2. using Lucene.Net.Documents;
  3. using Lucene.Net.Store;
  4. using Masuit.LuceneEFCore.SearchEngine.Extensions;
  5. using Masuit.LuceneEFCore.SearchEngine.Interfaces;
  6. using Microsoft.EntityFrameworkCore;
  7. using Microsoft.Extensions.Caching.Memory;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Linq;
  12. using System.Linq.Expressions;
  13. using System.Reflection;
  14. using System.Threading.Tasks;
  15. namespace Masuit.LuceneEFCore.SearchEngine
  16. {
  17. /// <summary>
  18. /// 搜索引擎
  19. /// </summary>
  20. /// <typeparam name="TContext"></typeparam>
  21. public class SearchEngine<TContext> : ISearchEngine<TContext> where TContext : DbContext
  22. {
  23. /// <summary>
  24. /// 数据库上下文
  25. /// </summary>
  26. public TContext Context { get; }
  27. /// <summary>
  28. /// 索引器
  29. /// </summary>
  30. public ILuceneIndexer LuceneIndexer { get; }
  31. /// <summary>
  32. /// 索引搜索器
  33. /// </summary>
  34. public ILuceneIndexSearcher LuceneIndexSearcher { get; }
  35. /// <summary>
  36. /// 索引条数
  37. /// </summary>
  38. public int IndexCount => LuceneIndexer.Count();
  39. /// <summary>
  40. /// 搜索引擎
  41. /// </summary>
  42. /// <param name="context">数据库上下文</param>
  43. /// <param name="directory"></param>
  44. /// <param name="analyzer"></param>
  45. /// <param name="memoryCache"></param>
  46. public SearchEngine(TContext context, Directory directory, Analyzer analyzer, IMemoryCache memoryCache)
  47. {
  48. Context = context;
  49. LuceneIndexer = new LuceneIndexer(directory, analyzer);
  50. LuceneIndexSearcher = new LuceneIndexSearcher(directory, analyzer, memoryCache);
  51. }
  52. /// <summary>
  53. /// 检查数据库上下文更改,并返回LuceneIndexChanges类型的集合
  54. /// </summary>
  55. /// <returns> LuceneIndexChangeset - 转换为LuceneIndexChanges类型的实体更改集合</returns>
  56. private LuceneIndexChangeset GetChangeset()
  57. {
  58. var changes = new LuceneIndexChangeset();
  59. foreach (var entity in Context.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged))
  60. {
  61. var entityType = entity.Entity.GetType();
  62. if (!typeof(ILuceneIndexable).IsAssignableFrom(entityType) || entityType.GetMethod("ToDocument") is null)
  63. {
  64. continue;
  65. }
  66. var change = new LuceneIndexChange(entity.Entity as ILuceneIndexable);
  67. switch (entity.State)
  68. {
  69. case EntityState.Added:
  70. change.State = LuceneIndexState.Added;
  71. break;
  72. case EntityState.Deleted:
  73. change.State = LuceneIndexState.Removed;
  74. break;
  75. case EntityState.Modified:
  76. change.State = LuceneIndexState.Updated;
  77. break;
  78. default:
  79. change.State = LuceneIndexState.Unchanged;
  80. break;
  81. }
  82. changes.Entries.Add(change);
  83. }
  84. return changes;
  85. }
  86. /// <summary>
  87. ///获取文档的具体版本
  88. /// </summary>
  89. /// <param name ="doc">要转换的文档</param>
  90. /// <returns></returns>
  91. private ILuceneIndexable GetConcreteFromDocument(Document doc)
  92. {
  93. var t = Type.GetType(doc.Get("Type"));
  94. var obj = Expression.Lambda<Func<ILuceneIndexable>>(Expression.New(t.GetConstructors()[0])).Compile()();
  95. foreach (var p in t.GetProperties().Where(p => p.GetCustomAttributes<LuceneIndexAttribute>().Any()))
  96. {
  97. p.SetValue(obj, doc.Get(p.Name, p.PropertyType));
  98. }
  99. return obj;
  100. }
  101. /// <summary>
  102. /// 保存数据更改并同步索引
  103. /// </summary>
  104. /// <returns></returns>
  105. public int SaveChanges(bool index = true)
  106. {
  107. int result = 0;
  108. if (Context.ChangeTracker.HasChanges())
  109. {
  110. // 获取要变更的实体集
  111. var changes = GetChangeset();
  112. result = Context.SaveChanges();
  113. if (changes.HasChanges && index)
  114. {
  115. LuceneIndexer.Update(changes);
  116. }
  117. }
  118. return result;
  119. }
  120. /// <summary>
  121. /// 保存数据更改并同步索引
  122. /// </summary>
  123. /// <param name="index">是否需要被重新索引</param>
  124. /// <returns></returns>
  125. public async Task<int> SaveChangesAsync(bool index = true)
  126. {
  127. int result = 0;
  128. if (Context.ChangeTracker.HasChanges())
  129. {
  130. // 获取要变更的结果集
  131. var changes = GetChangeset();
  132. result = await Context.SaveChangesAsync();
  133. if (changes.HasChanges && index)
  134. {
  135. LuceneIndexer.Update(changes);
  136. }
  137. }
  138. return result;
  139. }
  140. /// <summary>
  141. /// 扫描数据库上下文并对所有已实现ILuceneIndexable的对象,并创建索引
  142. /// </summary>
  143. public void CreateIndex()
  144. {
  145. if (LuceneIndexer == null)
  146. {
  147. return;
  148. }
  149. var index = new List<ILuceneIndexable>();
  150. var properties = Context.GetType().GetProperties();
  151. foreach (var pi in properties)
  152. {
  153. if (typeof(IEnumerable<ILuceneIndexable>).IsAssignableFrom(pi.PropertyType))
  154. {
  155. var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null);
  156. index.AddRange(entities as IEnumerable<ILuceneIndexable>);
  157. }
  158. }
  159. if (index.Any())
  160. {
  161. LuceneIndexer.CreateIndex(index);
  162. }
  163. }
  164. /// <summary>
  165. /// 创建指定数据表的索引
  166. /// </summary>
  167. public void CreateIndex(List<string> tables)
  168. {
  169. if (LuceneIndexer == null)
  170. {
  171. return;
  172. }
  173. var index = new List<ILuceneIndexable>();
  174. var properties = Context.GetType().GetProperties();
  175. foreach (var pi in properties)
  176. {
  177. if (typeof(IEnumerable<ILuceneIndexable>).IsAssignableFrom(pi.PropertyType) && tables.Contains(pi.Name))
  178. {
  179. var entities = Context.GetType().GetProperty(pi.Name).GetValue(Context, null);
  180. index.AddRange(entities as IEnumerable<ILuceneIndexable>);
  181. }
  182. }
  183. if (index.Any())
  184. {
  185. LuceneIndexer.CreateIndex(index);
  186. }
  187. }
  188. /// <summary>
  189. /// 删除索引
  190. /// </summary>
  191. public void DeleteIndex()
  192. {
  193. LuceneIndexer?.DeleteAll();
  194. }
  195. /// <summary>
  196. /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型,但不返回任何评分信息
  197. /// </summary>
  198. /// <typeparam name ="T">要搜索的实体类型 - 注意:必须实现ILuceneIndexable </typeparam>
  199. /// <param name ="options">搜索选项</param>
  200. /// <returns></returns>
  201. public ISearchResultCollection<T> Search<T>(SearchOptions options)
  202. {
  203. options.Type = typeof(T);
  204. var indexResults = LuceneIndexSearcher.ScoredSearch(options);
  205. ISearchResultCollection<T> resultSet = new SearchResultCollection<T>
  206. {
  207. TotalHits = indexResults.TotalHits
  208. };
  209. var sw = Stopwatch.StartNew();
  210. foreach (var indexResult in indexResults.Results)
  211. {
  212. var entity = (T)GetConcreteFromDocument(indexResult.Document);
  213. resultSet.Results.Add(entity);
  214. }
  215. sw.Stop();
  216. resultSet.Elapsed = indexResults.Elapsed + sw.ElapsedMilliseconds;
  217. return resultSet;
  218. }
  219. /// <summary>
  220. /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型,但不返回任何评分信息
  221. /// </summary>
  222. /// <typeparam name ="T">要搜索的实体类型 - 注意:必须实现ILuceneIndexable </typeparam>
  223. /// <param name ="options">搜索选项</param>
  224. /// <returns></returns>
  225. public IScoredSearchResultCollection<T> ScoredSearch<T>(SearchOptions options)
  226. {
  227. // 确保类型匹配
  228. if (typeof(T) != typeof(ILuceneIndexable))
  229. {
  230. options.Type = typeof(T);
  231. }
  232. var indexResults = LuceneIndexSearcher.ScoredSearch(options);
  233. IScoredSearchResultCollection<T> results = new ScoredSearchResultCollection<T>();
  234. results.TotalHits = indexResults.TotalHits;
  235. var sw = Stopwatch.StartNew();
  236. foreach (var indexResult in indexResults.Results)
  237. {
  238. IScoredSearchResult<T> result = new ScoredSearchResult<T>();
  239. result.Score = indexResult.Score;
  240. result.Entity = (T)GetConcreteFromDocument(indexResult.Document);
  241. results.Results.Add(result);
  242. }
  243. sw.Stop();
  244. results.Elapsed = indexResults.Elapsed + sw.ElapsedMilliseconds;
  245. return results;
  246. }
  247. /// <summary>
  248. /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型
  249. /// </summary>
  250. /// <param name ="options">搜索选项</param>
  251. /// <returns></returns>
  252. public IScoredSearchResultCollection<ILuceneIndexable> ScoredSearch(SearchOptions options)
  253. {
  254. return ScoredSearch<ILuceneIndexable>(options);
  255. }
  256. /// <summary>
  257. /// 执行搜索并将结果限制为特定类型,在返回之前,搜索结果将转换为相关类型
  258. /// </summary>
  259. /// <param name ="options">搜索选项</param>
  260. /// <returns></returns>
  261. public ISearchResultCollection<ILuceneIndexable> Search(SearchOptions options)
  262. {
  263. return Search<ILuceneIndexable>(options);
  264. }
  265. /// <summary>
  266. /// 搜索一条匹配度最高的记录
  267. /// </summary>
  268. /// <param name ="options">搜索选项</param>
  269. /// <returns></returns>
  270. public ILuceneIndexable SearchOne(SearchOptions options)
  271. {
  272. return GetConcreteFromDocument(LuceneIndexSearcher.ScoredSearchSingle(options));
  273. }
  274. /// <summary>
  275. /// 搜索一条匹配度最高的记录
  276. /// </summary>
  277. /// <param name ="options">搜索选项</param>
  278. /// <returns></returns>
  279. public T SearchOne<T>(SearchOptions options) where T : class
  280. {
  281. return GetConcreteFromDocument(LuceneIndexSearcher.ScoredSearchSingle(options)) as T;
  282. }
  283. }
  284. }