从零开始搭建.NET Core版搜索引擎(四)--与数据库实体关联

19 篇文章 7 订阅
11 篇文章 4 订阅

经过上次的改造,可以实现对实体对象中的特定成员创建索引,但从实际的应用上来看,需要检索的数据内容格式多种多样,可能会有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:

RowIdCompanyNameCompanyAddress
1南京大地建设有限公司南京市玄武区北京东路12号
2江苏中南实业有限公司南京市鼓楼区上海路7号

对应到索引Company中是两个Document:

  • Document1
FieldNameFieldValue
CompanyName南京大地建设有限公司
CompanyAddress南京市玄武区北京东路12号

第一个Field的值“南京大地建设有限公司”可以被分割为“南京”、“大地”、“建设”、“有限”、“公司”这几个Term。
第二个Filed的值“南京市玄武区北京东路12号”可以被分割为“南京”、“南京市”、“玄武”、“玄武区”、“北京”、“北京东路”、“东路”、“12”、“号”这几个Term。

  • Document2
FieldNameFieldValue
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, "<[^>]+>", "");
        }


项目地址:https://github.com/ludewig/Muyan.Search

每天进步一点点,翘首期盼明天。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值