128  
查询码:00000968
基于Lucene.Net构建.NET Core版搜索引擎(十二)--完善索引创建更新机制
作者: 俞鹏 于 2022年01月27日 发布在分类 / 人防组 / 人防后端 下,并于 2022年01月27日 编辑
Lucene.Net

在上一篇文章中,简单的做了一个测试,对已有项目进行改造。测试完发现,对于已有数据只能采用全部重建索引的方式。如果是初始化的项目,可以在创建、更新数据库实体的同时同步创建、更新索引,这样既能确保数据库实体与索引的同步,也能将创建索引的时间平均分配至每次单元操作中。在业务流程上也要尽量避免频繁的重建全部索引。所以,在本篇文章中将解决这个问题:

怎么实现索引的同步呢?这里,我们不可能将代码写到每个接口方法中,这样,代码太冗余了。既然是对实体的操作,能不能在上下文SaveChange之前或者之后做一些事情呢?这里我将对AuditLogDbContext加一些代码,注意AuditLogDbContext是继承AbpDbContext,只需要重写SaveChanges()或者SaveChangesAsync(),在里面加上索引同步的代码即可。

改造AuditLogDbContext

1.添加SearchEntityAttribute特性

/// <summary>
/// 用于数据库实体,加上此标识,表示该实体需要进行全文检索
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)]
public class SearchEntityAttribute : Attribute
{
    /// <summary>
    /// 是否启用全文检索
    /// </summary>
    public bool IsEnabled { get; set; } = true;

    /// <summary>
    /// 构造函数
    /// </summary>
    public SearchEntityAttribute()
    {
        IsEnabled = true;
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="isEnabled"></param>
    public SearchEntityAttribute(bool isEnabled)
    {
        IsEnabled = isEnabled;
    }
}  

2.改造BeforeSaveChanges()方法

保存数据前的操作,判断实体跟踪的状态,如果是增删改的状态,则判断该实体有没有标记SearchEntityAttribute 特性,如果有,则记录该实体的数据。

/// <summary>
/// 保存数据前操作
/// </summary>
/// <returns></returns>
protected Task BeforeSaveChanges()
{   
    if (AuditConfig.AuditConfigOptions.AuditEnabled)// && AuditConfig.AuditConfigOptions.Stores.Count > 0
    {
        foreach (var entityEntry in ChangeTracker.Entries())
        {         
            if (entityEntry.State == EntityState.Detached || entityEntry.State == EntityState.Unchanged)
            {
                continue;
            }
            //
            if (AuditConfig.AuditConfigOptions.EntityFilters.Any(entityFilter =>
                entityFilter.Invoke(entityEntry) == false))
            {
                continue;
            }
            //判断实体中有LogEntity特性,如果有且IsEnabled==true就记录日志
            var attribuate= entityEntry.Entity.GetType().GetCustomAttribute<LogEntityAttribute>();
            if (attribuate != null&& attribuate.IsEnabled)
            {
                AuditEntries.Add(new InternalAuditEntry(entityEntry));
            }
            //判断实体中有SearchEntity特性,如果有且IsEnabled==true就创建索引
            var attribuate2 = entityEntry.Entity.GetType().GetCustomAttribute<SearchEntityAttribute>();
            if (attribuate2 != null && attribuate2.IsEnabled)
            {
                SearchEntries.Add(new InternalAuditEntry(entityEntry));
            }
        }
    }
    return Task.CompletedTask;
} 

3.改造AfterSaveChanges()方法

保存数据后操作,将BeforeSaveChanges 保存的数据,同步到索引文件中。

/// <summary>
/// 保存数据后操作
/// </summary>
/// <returns></returns>
protected Task AfterSaveChanges()
{
    //记录实体变更日志
    this.WriteToLog();
    //创建(追加)或修改实体索引
    this.WriteToLucene();
    return Task.CompletedTask;
}

具体来看一下WriteToLucene()方法:

/// <summary>
/// 创建(追加)或修改实体索引
/// </summary>
private void WriteToLucene()
{
    if (null != SearchEntries && SearchEntries.Count > 0)
    {
        foreach (var entry in SearchEntries)
        {
            if (entry is InternalAuditEntry searchEntry)
            {
                switch (searchEntry.OperationType)
                {
                    case DataOperationType.Add:
                        //这里默认:如果不存在索引,则创建新索引,否则将打开索引并追加文档
                        _searchManager.CreateIndexByEntity(entry.Entities);
                        break;
                    case DataOperationType.Delete:
                        var type_d = entry.Entities.GetType();
                        //获取entityId
                        var entityId_d = type_d.GetProperty("Id").GetValue(entry.Entities).ToString();
                        _searchManager.DeleteIndex(entityId_d);
                        break;
                    case DataOperationType.Update:
                        var type_u = entry.Entities.GetType();
                        //获取entityId
                        var entityId_u = type_u.GetProperty("Id").GetValue(entry.Entities).ToString();
                        var deleted_n = type_u.GetProperty("IsDeleted").GetValue(entry.Entities).ToString();
                        bool.TryParse(deleted_n, out bool isDeleted);
                        //如果是软删除,IsDeleted=1
                        if (isDeleted)
                        {
                            _searchManager.DeleteIndex(entityId_u);
                        }
                        //如果是修改
                        else
                        {
                            //这里默认:如果不存在索引,则创建新索引,否则将打开索引并追加文档
                            _searchManager.CreateIndexByEntity(entry.Entities);
                        }
                        break;
                }
            }
        }
    }
}

4.重写SaveChanges()方法

/// <summary>
/// 重写SaveChanges
/// </summary>
/// <returns></returns>
public override int SaveChanges()
{
    BeforeSaveChanges().ConfigureAwait(false).GetAwaiter().GetResult();
    var result = base.SaveChanges();
    if (result>0)
    {
        AfterSaveChanges().ConfigureAwait(false).GetAwaiter().GetResult();
    }
    return result;
}

到此, 改造 AuditLogDbContext的工作就已经完成了……

5.实际应用

在实际的代码怎么去应用呢?

首先安装nuget包:RunGo.Security最新版,然后找到项目的实体层RunGo.xxx.EntityFrameworkCore中的xxxDbContext,继承自AuditLogDbContext,最后在需要同步索引的实体类上加上 SearchEntityAttribute 特性。

public class PersonnelDbContext : AuditLogDbContext
{
      //todo……
}
[SearchEntity]
public class Archive : FullAuditedEntity<string>, IMayBeHaveTenant, IEntity<string>
{
     //todo……
}


 推荐知识

 历史版本

修改日期 修改人 备注
2022-01-27 17:16:01[当前版本] 俞鹏 V1.0
2022-01-27 17:14:16 俞鹏 V1.0
2022-01-27 17:06:24 俞鹏 V1.0
2022-01-27 17:05:41 俞鹏 V1.0

  目录
    知识分享平台 -V 4.8.7 -wcp