经过上次的改造,可以实现对实体对象中的特定成员创建索引,但从实际的应用上来看,需要检索的数据内容格式多种多样,可能会有HTML、XML等。另外一些自定义的数据结构可能是以JSON等特殊规则形式存储的,对于这些情况就需要单独对数据进行分割处理。
1.索引基本概念回顾
1.1.索引文件结构
索引文件的结构类似上图中的树状结构:
-
索引(Index)
-
段(Segment)
-
文档(Document)
-
域(Field)
- 词(Term)
-
-
-
而为了达到快速检索查询的目的,Lucene.NET的数据结构可不止这么简单。在后续的文章了让我们一步步解密吧。
1.2.常用Field
- StringField:字符串类型Field, 不分词, 作为一个整体进行索引(如: 身份证号, 订单编号), 是否需要存储由Store.YES或Store.NO决定
new StringField(“sField”, StringField, Field.Store.YES);
- LongField:Long数值型Field代表, 分词并且索引(如: 价格), 是否需要存储由Store.YES或Store.NO决定
new LongField(“lField”, LongField, Field.Store.YES);
- StoredField:构建不同类型的Field, 不分词, 不索引, 要存储. (如: 文件附件路径)
new StoredField(“sField2”, StoredField, Field.Store.YES);
- TextField:文本类型Field, 分词并且索引, 是否需要存储由Store.YES或Store.NO决定
new TextField(“tField”, TextField, Field.Store.YES);
- Field:自定义是否存储、索引、分类、设置权重等
FieldType fieldType = new FieldType();// 重构FieldType类
fieldType.setIndexed(true);// set 是否索引
fieldType.setStored(true);// set 是否存储
fieldType.setTokenized(true);// set 是否分词
fieldType.setOmitNorms(false);// set 是否可以设置权重
Field field = new Field(“Field”, Field, fieldType);
1.3.数据库表与索引的关系
当为数据实体创建索引时,我们不仅要了解索引文件的结构,也要了解索引与数据库实体的关系,这样才能更好的利用索引进行查询检索。
上图描述了数据库表与索引文件的大致对应关系,索引Index对应数据库表Table,文档Document对应数据库表中的一条记录Row,域Field对应这条记录中的每个列字段Column。而词Term则是将列字段值分词后端结果。
举个例子:
数据库表Company:
RowId | CompanyName | CompanyAddress |
---|---|---|
1 | 南京大地建设有限公司 | 南京市玄武区北京东路12号 |
2 | 江苏中南实业有限公司 | 南京市鼓楼区上海路7号 |
对应到索引Company中是两个Document:
- Document1
FieldName | FieldValue |
---|---|
CompanyName | 南京大地建设有限公司 |
CompanyAddress | 南京市玄武区北京东路12号 |
第一个Field的值“南京大地建设有限公司”可以被分割为“南京”、“大地”、“建设”、“有限”、“公司”这几个Term。
第二个Filed的值“南京市玄武区北京东路12号”可以被分割为“南京”、“南京市”、“玄武”、“玄武区”、“北京”、“北京东路”、“东路”、“12”、“号”这几个Term。
- Document2
FieldName | FieldValue |
---|---|
CompanyName | 江苏中南实业有限公司 |
CompanyAddress | 南京市鼓楼区上海路7号 |
Document2中Field中值的分割也与Document1类似。
注意:数据库表与索引的对应结构并不总是这样的,Document、Field的创建和Term的分割是可以自定义的。另外Document并不像数据实体那样存在唯一标识,需要自己实现为业务数据建立关系。
2.为不同数据结构创建索引
2.1.定义数据类型枚举
public enum FieldDataType
{
/// <summary>
/// Html文本
/// </summary>
Html,
/// <summary>
/// JSON文本
/// </summary>
Json,
/// <summary>
/// 纯文本
/// </summary>
Text,
/// <summary>
/// Xml文本
/// </summary>
Xml,
/// <summary>
/// Csv文本
/// </summary>
Csv,
/// <summary>
/// 年份yyyy
/// </summary>
DateYear,
/// <summary>
/// 时间
/// </summary>
DateTime,
/// <summary>
/// 数字
/// </summary>
Int32,
/// <summary>
/// 数字
/// </summary>
Int64,
/// <summary>
/// 小数
/// </summary>
Double
}
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; }
}
2.3.根据实体字段的数据类型创建索引
/// <summary>
/// 为实体创建索引
/// </summary>
/// <param name="entity"></param>
/// <param name="isFiltered">是否启用过滤</param>
public virtual void CreateIndexByEntity(IEntity<string> entity,bool isFiltered=true)
{
var config = new IndexWriterConfig(LuceneVersion.LUCENE_48, Analyzer);
using (IndexWriter writer = new IndexWriter(Directory, config))
{
//创建文档
Document doc = new Document();
var type = entity.GetType();
//为实体所在的类的名称创建Field,目的是对实体进行标识,便于以后检索
doc.Add(new StringField(CoreConstant.EntityType,type.AssemblyQualifiedName,Field.Store.YES));
var properties = type.GetProperties();
//遍历实体的成员集合
foreach (var propertyInfo in properties)
{
var propertyValue = propertyInfo.GetValue(entity);
if (propertyValue==null)
{
continue;
}
string fieldName = propertyInfo.Name;//成员字段名称
if (isFiltered)
{
var attributes = propertyInfo.GetCustomAttributes<IndexAttribute>();//获取自定义属性集合
foreach (var attribute in attributes)
{
string name = string.IsNullOrEmpty(attribute.FieldName) ? fieldName : attribute.FieldName;
switch (attribute.FieldType)
{
case FieldDataType.DateTime:
doc.Add(new StringField(fieldName, ((DateTime)propertyValue).ToString("yyyy-MM-dd HH:mm:ss"), attribute.IsStore));
break;
case FieldDataType.DateYear:
break;
case FieldDataType.Int32:
doc.Add(new Int32Field(fieldName, (Int32)propertyValue, attribute.IsStore));
break;
case FieldDataType.Int64:
doc.Add(new Int64Field(fieldName, (Int64)propertyValue, attribute.IsStore));
break;
case FieldDataType.Double:
doc.Add(new DoubleField(fieldName, (double)propertyValue, attribute.IsStore));
break;
case FieldDataType.Html:
doc.Add(new TextField(fieldName, propertyValue.ToString().ClearHtml(), attribute.IsStore));
break;
case FieldDataType.Json:
doc.Add(new TextField(fieldName, propertyValue.ToString().ClearJson(), attribute.IsStore));
break;
case FieldDataType.Xml:
doc.Add(new TextField(fieldName, propertyValue.ToString().ClearXml(), attribute.IsStore));
break;
case FieldDataType.Csv:
doc.Add(new TextField(fieldName, propertyValue.ToString().ClearCsv(), attribute.IsStore));
break;
default:
doc.Add(new TextField(fieldName, propertyValue.ToString(), attribute.IsStore));
break;
}
}
}
else
{
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.4.不同类型字段的预处理
/// <summary>
/// 移除指定字符
/// </summary>
/// <param name="source"></param>
/// <param name="chars"></param>
/// <returns></returns>
internal static string ClearChar(this string source, IEnumerable<char> chars)
{
return string.IsNullOrEmpty(source)
? string.Empty
: new string(source.Where(t => !chars.Contains(t)).ToArray());
}
/// <summary>
/// 移除HTML标签
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
internal static string ClearHtml(this string source)
{
string result = Regex.Replace(source, "<[^>]+>", "");
return Regex.Replace(result, "&[^;]+;", "");
}
/// <summary>
/// 移除XML标签
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
internal static string ClearXml(this string source)
{
return Regex.Replace(source, "<[^>]+>", "");
}
每天进步一点点,翘首期盼明天。