起源

之前做的很多项目都使用solr/elasticsearch作为全文检索引擎,它们功能全面而强大,但是对于较小的项目而言,构建和维护成本显然过高,尤其是从关系数据库/文档数据库到全文检索引擎的数据同步工作非常繁琐,且容易出错。

记得很久以前就知道postgresql数据库内置全文检索,最近发现这个数据库越来越火,于是就又研究了一番,欣喜的发现居然支持ef core,于是对其进行了一些研究,并整理心得如下。

前提

本文假设读者熟悉entity framework core的基本概念和基本使用。

目的

建立dotnet core项目,使用postgres数据库和ef core,实现常见的全文检索功能,包括

  • 建立索引字段
  • 基本查询
  • 查询结果排名
  • 查询结果高亮显示

步骤1 - 新建项目并引入packages

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3" />
</ItemGroup> </Project>

注意NamingConventions包是可选的,其作用是将表和字段名称翻译成蛇形,如MyData -> my_data,这样比较方便手写sql,不用写烦人的引号。

步骤2 - 建立model和dbcontext

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using NpgsqlTypes; public class Article
{
public int Id { get; set; } [Required]
[MaxLength()]
public string Title { get; set; } [MaxLength()]
public string Abst { get; set; } public NpgsqlTsVector TitleVector { get; set; }
public NpgsqlTsVector AbstVector { get; set; } [NotMapped]
public string TitleHL { get; set; } [NotMapped]
public string AbstHL { get; set; }
}

本model中的TitleVector和AbstVector分别用来存放Title和Abst字段的分词结果,便于后续的查询。不必担心代码会不小心改掉这些字段以至于查询出错,因为后续会设置一个触发器,每次更改数据的时候都会自动更新这些字段的内容。

using Microsoft.EntityFrameworkCore;

public class MyDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
.UseNpgsql("Host=localhost;Database=ft;Username=postgres;Password=123456")
.UseLoggerFactory(PgFtSearch.Program.MyLoggerFactory)
.UseSnakeCaseNamingConvention(); protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Entity<Article>().HasIndex(p => p.TitleVector).HasMethod("GIN");
modelBuilder.Entity<Article>().HasIndex(p => p.AbstVector).HasMethod("GIN");
} public DbSet<Article> Articles { get; set; }
}

首先UseNpgsql设置了要连接哪个数据库,然后UseLoggerFactory用来打印日志,主要是sql语句。MyLoggerFactory是怎么来的,参考后续的代码。

GIN的两行,用来告诉数据库这两个字段是采用倒排索引。

步骤3 - 生成migration并手动添加触发器

dotnet ef migrations add Init

然后,在生成的migration文件中手动添加触发器,在新增或者修改数据时,自动修改索引字段的内容,应用程序不必担心索引同步的问题。

migrationBuilder.Sql(
@"CREATE TRIGGER article_title_search_vector_update BEFORE INSERT OR UPDATE
ON articles FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(title_vector, 'pg_catalog.english', title);"); migrationBuilder.Sql(
@"CREATE TRIGGER article_abst_search_vector_update BEFORE INSERT OR UPDATE
ON articles FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(abst_vector, 'pg_catalog.english', abst);");

步骤4 - 编写程序

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; namespace PgFtSearch
{
class Program
{
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder => { builder.AddConsole(); }); static void Main(string[] args)
{
using (var db = new MyDbContext())
{
if (!db.Articles.Any())
{
var articles = new List<Article>{
new Article{Title="testing is ok", Abst="this is a test about postgre full text searching"},
new Article{Title="tested all bugs", Abst="there is no bug exists in this app"}
}; db.AddRange(articles);
db.SaveChanges();
} var query = "test"; var data = db.Articles
.Where(p => p.TitleVector.Matches(query) || p.AbstVector.Matches(query))
.OrderByDescending(p=>p.TitleVector.Rank(EF.Functions.ToTsQuery(query)) * 2.0 + p.AbstVector.Rank(EF.Functions.ToTsQuery(query)))
.Select(p=>new Article{
Title = p.Title,
Abst = p.Abst,
TitleHL = EF.Functions.ToTsQuery(query).GetResultHeadline(p.Title),
AbstHL = EF.Functions.ToTsQuery(query).GetResultHeadline(p.Abst),
}); foreach (var article in data)
{
Console.WriteLine($"{article.Title}\t{article.Abst}\t{article.TitleHL}\t{article.AbstHL}");
}
}
}
}
}

首先,如果没有数据,插入几条测试数据。

下面到了最关键的地方,编写数据查询的代码,实现的具体功能是:

  • 使用test关键字在title或abst字段中查询数据
  • 对查询结果进行排序,title字段排序权重=2.0,高于abst字段权重=1.0
  • 检索结果的title和abst进行高亮显示

最终生成的SQL如下:

SELECT 
  a.title AS "Title",
  a.abst AS "Abst",
  ts_headline(a.title, to_tsquery(@__query_0)) AS "TitleHL",
  ts_headline(a.abst, to_tsquery(@__query_0)) AS "AbstHL"
FROM articles AS a
WHERE (a.title_vector @@ plainto_tsquery(@__query_0)) OR (a.abst_vector @@ plainto_tsquery(@__query_0))
ORDER BY (ts_rank(a.title_vector, to_tsquery(@__query_0))::double precision * 2.0) + ts_rank(a.abst_vector, to_tsquery(@__query_0))::double precision DESC

代码在这儿,相信大家都能看懂,有问题欢迎交流。

总结

目前还未研究中文分词的支持情况,也没有测试性能。不过大致看来,完全可以在中小型项目中使用postgres数据库的内置全文检索功能替代solr/es等搜索引擎,减少系统的复杂程度,提升全文检索功能的稳定性。

在ef core中使用postgres数据库的全文检索功能实战的更多相关文章

  1. 在ef core中使用postgres数据库的全文检索功能实战之中文支持

    前言 有关通用的postgres数据库全文检索在ef core中的使用方法,参见我的上一篇文章. 本文实践了zhparser中文插件进行全文检索. 准备工作 安装插件,最方便的方法是直接使用安装好插件 ...

  2. EF Core中如何设置数据库表自己与自己的多对多关系

    本文的代码基于.NET Core 3.0和EF Core 3.0 有时候在数据库设计中,一个表自己会和自己是多对多关系. 在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句 ...

  3. EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的

    我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...

  4. EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况

    使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...

  5. EF Core中怎么实现自动更新实体的属性值到数据库

    我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...

  6. [小技巧]EF Core中如何获取上下文中操作过的实体

    原文地址:https://www.cnblogs.com/lwqlun/p/10576443.html 作者:Lamond Lu 源代码:https://github.com/lamondlu/EFC ...

  7. EF Core中避免贫血模型的三种行之有效的方法(翻译)

    Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...

  8. EF Core中DbContext可以被Dispose多次

    我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...

  9. 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)

    原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ...

随机推荐

  1. python与excel的关系;铁打的python流水的excel

    现在很多行业,都离不开用Excel: 做财务的,要用Excel做报表:做物流的,会用Excel来跟踪订单情况:做HR的,会用Excel算工资:做分析的,会用Excel计算数据做报表.不知道你有没有这样 ...

  2. Java中的OOM问题

    OOM是什么 OOM全称"OutOfMemory",既内存溢出.我们知道,Java中的对象是在堆(heap)上创建的,当堆内存不足以为新创建的对象分配空间时,就会产生OutOfMe ...

  3. 云开发静态网站托管现已支持 Angular 应用

    云开发静态托管是云开发提供的静态网站托管的能力,静态资源(HTML.CSS.JavaScript.字体等)的分发由腾讯云对象存储 COS 和拥有多个边缘网点的腾讯云 CDN 提供支持. 在云开发静态托 ...

  4. js 异或加密

    // 按位异或 加密         var posNo = 'C0041710190002'         // 特殊字符不进行 与或 加密 (因为A,a,Z,z,转换后会变成符号,不方面用户输入 ...

  5. php算--------法

    <?php //冒泡排序:两两交换数值,最小的值在最左边,就如最轻的气泡在最上边.对整列数两两交换一次//最小的数在最左边,每次都能得一个在剩下的数中的最小 的数//“冒”出来的数组成一个有序区 ...

  6. Qt提示:setLayout: Attempting to set QLayout "" on MainWindow "MainWindow", which already has a layout

    如题,出现这个的原因是,如果你的窗口继承的是QMainwindow,需要设置setCentralWidget(); 如下: QWidget * widget = new QWidget ( mainW ...

  7. material UI中子组件样式修改的几种方案研究

      material UI是一个流行的与React配套的前端UI框架,对于开发者而言,熟悉它的样式修改方案是必要的.但目前相关资料并不直观,并且没有总结到一起.如果对相关特性不太清楚,开发者很可能会在 ...

  8. layui模块化加载Echarts图表v4.2.1

    layui.use(['jquery','echarts'], function () { var $ = layui.$; //记得这是dom对象不是JQ对象,需要转换 echarts = layu ...

  9. 硬纪元AI峰会前瞻:如何才能做好智能家居?用户体验最重要

    用户体验不到位,市场就不能说真的发展起来. 可以明显的感觉到,随着人工智能.物联网等技术的发展和应用,我们的生活正在发生翻天覆地的变化,其中感觉最为明显的就是智能家居. 据前瞻产业研究院的数据统计,我 ...

  10. 两种方法直接删除数组中特定值的项(JavaScript)

    一.问题详情: 直接删除意为原数组需要被改变,而不是得到另一个数组. 二.JavaScript实现 (一)巧用数组的push( ).shift( )方法 function del(arr,num) { ...