经过上一篇的改造,可以作为公共方法使用了,但是通用性和抽象性还不够,所以继续从通用性着手来扩展方法。
1.泛型扩展
对前面的代码进行分析发现,多个方法都涉及到分析器Analyzer、存储目录Directory、索引操作器配置IndexWriterConfig的实例化,那么就考虑将这些对象作为属性抽离出来。
1.1.公共属性提取
/// <summary>
/// 分析器
/// </summary>
public Analyzer Analyzer { get; set; }
/// <summary>
/// 存储
/// </summary>
public Directory Directory { get; set; }
其中分析器Analyzer和存储路径Directory可以在构造函数中实例化。公共属性提取后整个操作类中的相关方法就修改为如下所示。
public SearchManager(Directory directory, Analyzer analyzer)
{
Directory = directory;
Analyzer = analyzer;
IndexConfig = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer);
}
#region 创建索引
/// <summary>
/// 创建索引
/// </summary>
/// <param name="fields">Field域集合,也可以是其他结构</param>
public virtual void CreateIndex(Dictionary<string, string> fields)
{
using (IndexWriter writer = new IndexWriter(Directory, IndexConfig))
{
//创建文档
Document doc = new Document();
foreach (var field in fields)
{
//创建数据域。此处使用了StringField域。此外还能用Int32Field、DoubleField等。
//StringField:将字段索引,但不会进行分词。
//TextField:将字段分词后进行索引
Field f = new TextField(field.Key, field.Value, Field.Store.YES);
//添加数据域至文档
doc.Add(f);
}
//索引操作器增加文档
writer.AddDocument(doc);
//刷新索引
writer.Flush(true, true);
writer.Commit();
}
}
#endregion
1.2.扩展为泛型方法
通过研究Lucene.NET的代码发现,分析器Analyzer及存储路径Directory均为基类,各自下面还有一系列的派生类。
分析器Analyzer的派生类对应的类库有:
这是Lucene.NET内置的分析器:
- WhiteSpaceAnalyzer:仅去除空格,对字符没有lowercase化,不支持中文,也不支持破折号分词。
- SimpleAnayzer:功能强于WhiteSpaceAnalyzer,提供对字符的lowercase化,不支持中文,保留停用词。
- StopAnalyzer:功能强于SimpleAnayzer,增加了停用词功能,不支持中文。
- StandardAnalyzer:英文处理能力增强,保留了email地址及XY&Z的形式,支持中文采用单词分词。
- ChineseAnalyzer:中文分词器,一个汉字是一个token。
- CJKAnalyzer:中文分词器,两个汉字是一个token。
- SmartChineseAnalyzer:中文分词器,每一个词语是一个token。
除此之外还有很多第三方的分析器,比如盘古PanGu、结巴JieBa、IKAnalyzer等。
存储Directory的派生类有:
可以存储的位置包括文件、内存等。
因此将分析器Analyzer及存储路径Directory修改为泛型,整个操作类也修改为泛型类。
public interface ISearchManager<TAnalyzer,TDirectory> where TAnalyzer:Analyzer where TDirectory:Directory
{
/// <summary>
/// 分析器
/// </summary>
TAnalyzer Analyzer { get; set; }
/// <summary>
/// 存储
/// </summary>
TDirectory Directory { get; set; }
/// <summary>
/// 创建索引
/// </summary>
/// <param name="fields"></param>
void CreateIndex(Dictionary<string, string> fields);
/// <summary>
/// 删除索引
/// </summary>
/// <param name="docId">文档</param>
void DeleteIndex(int docId);
/// <summary>
/// 删除所有索引
/// </summary>
void DeleteAllIndex();
/// <summary>
/// 更新索引
/// </summary>
/// <param name="doc">文档</param>
/// <param name="dic">要更新的域</param>
void UpdateIndex(Document doc, Dictionary<string, string> dic);
/// <summary>
/// 索引计数
/// </summary>
/// <returns></returns>
int CountIndex();
/// <summary>
/// 关键词查询
/// </summary>
/// <param name="keyword"></param>
/// <param name="field"></param>
/// <returns></returns>
Dictionary<Document, float> SearchIndex(string keyword, string field);
/// <summary>
/// 为实体对象创建索引
/// </summary>
/// <param name="entity"></param>
void CreateIndexByEntity(IEntity<string> entity);
}
public interface ISearchManager:ISearchManager<Analyzer,Directory>
{
}
2.为实体创建索引
在常见的业务逻辑中,使用的最多的是对数据实体对象的增删改查操作。我们希望在将实体插入到数据库之前,将实体对象的数据分割并索引化,便于以后的检索。
根据实体创建索引就不能只使用StringField或者TextField了,毕竟实体属性的数据类型可是多种多样,而且属性的名称也是无法预测的,因此就有必要创建一个单独的方法用于为实体创建索引。
2.1.创建索引(实体)方法
/// <summary>
/// 为实体创建索引
/// </summary>
/// <param name="entity"></param>
public virtual void CreateIndexByEntity(IEntity<string> entity)
{
using (IndexWriter writer = new IndexWriter(Directory, IndexConfig))
{
//创建文档
Document doc = new Document();
var type = entity.GetType();
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
var propertyValue = propertyInfo.GetValue(this);
if (propertyValue==null)
{
continue;
}
string fieldName = propertyInfo.Name;
switch (propertyValue)
{
case DateTime time:
doc.Add(new StringField(fieldName,time.ToString("yyyy-MM-dd HH:mm:ss"),Field.Store.YES));
break;
case int num:
doc.Add(new Int32Field(fieldName,num,Field.Store.YES));
break;
case long num:
doc.Add(new Int64Field(fieldName,num,Field.Store.YES));
break;
case double num:
doc.Add(new DoubleField(fieldName,num,Field.Store.YES));
break;
default:
doc.Add(new TextField(fieldName, propertyValue.ToString(),Field.Store.YES));
break;
}
}
writer.AddDocument(doc);
//刷新索引
writer.Flush(true, true);
writer.Commit();
}
}
2.2.索引属性标识
上面的方法存在一个小问题,那就是会为所有属性字段创建索引,但是实际使用中并不需要为所有属性字段创建索引,因此增加一个属性标签用于标记实体中需要创建索引的字段。
[AttributeUsage(AttributeTargets.Property)]
public class IndexAttribute:Attribute
{
public IndexAttribute()
{
IsStore = Field.Store.YES;
FieldType = FieldDataType.Text;
}
/// <summary>
/// 名称
/// </summary>
public string FieldName { get; set; }
/// <summary>
/// 是否存储
/// </summary>
public Field.Store IsStore { get; set; }
/// <summary>
/// 数据格式
/// </summary>
public FieldDataType FieldType { get; set; }
}
对属性字段进行如下标注:
public class DataEntity : Entity<string>
{
[Index(FieldName = "", IsStore = Field.Store.YES, FieldType = FieldDataType.Text)]
public virtual string DataValue { get; set; }
/// <summary>
/// 备注
/// </summary>
public virtual string Remark { get; set; }
}
本次改造先到这里,下次继续。