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;
}
}
}