190  
查询码:00000466
基于Lucene.Net构建.NET Core版搜索引擎(三)--泛型扩展及为实体创建索引
作者: 潘帅 于 2021年09月27日 发布在分类 / 人防组 / 人防后端 下,并于 2021年09月30日 编辑
Lucene.NET 搜索引擎

经过上一篇的改造,可以作为公共方法使用了,但是通用性和抽象性还不够,所以继续从通用性着手来扩展方法。


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




 推荐知识

 历史版本

修改日期 修改人 备注
2021-09-30 08:57:58[当前版本] 潘帅 1.1
2021-09-27 21:12:51 潘帅 1.1
2021-09-27 18:08:36 潘帅 1.0

 附件

附件类型

PNGPNG

知识分享平台 -V 4.8.7 -wcp