起源

之前做的很多项目都使用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. 搭建Ubuntu虚拟机

    搭建Ubuntu虚拟机 前言 1. 啰嗦一下 1.1 ubuntu虚拟机的作用 1.2 为什么选择Ubuntu 1.3 工具准备 2. 正式开始 2.1 安装VMware 2.2 创建Ubuntu虚拟 ...

  2. XML外部实体注入[转载]

    前言 对于xxe,深入的太少,一般做题也是复制payload再修改,没有了解过内部的结构规范等.这里转载了一篇先知社区的文章,排版了一下适合博客样式.文章总结的很好,结合了很多篇的博客文章,看完也是对 ...

  3. 刚从一道题发现的一些东西,PHP笔记,关于extract和null 空字符串

    队友发给我的一道extract变量的最基础的题目,他发现了一些问题,当传入shiyan=&flag=0时出flag,当传入shiyan=0&flag=0时不出flag,传入shiyan ...

  4. MVC-过滤器-权限认证

    过滤器主要基于特性,aop来实现对MVC管道中插入其他处理逻辑.比如,访问网站,需要检查是否已经登陆,若没登陆跳入登陆界面. 样例: 方法注册 执行效果 当不符合认证时: 上面是方法注册特性.还有类注 ...

  5. Git速查表大全

  6. Caused by: java.lang.NumberFormatException: For input string: " 60"

    原因 原原因:string转int 格式出错 解决:我的输入文件格式在根据“,”分割完之后多出了一个空格,我想要的是“60” 但是分割完之后是“ 60”所以导致格式转换不匹配.

  7. iconv 参数详解

    参数详解: $row [] = iconv('utf-8', 'GB2312//IGNORE', $value['message']); iconv ( string $in_charset , st ...

  8. ST3 package control

    view-> showconsole    (ctrl+`) import urllib.request,os,hashlib; h = 'df21e130d211cfc94d9b0905775 ...

  9. 什么是最好的在线UML软件工具?

    在线UML软件工具允许您创建UML图表,而UML绘图工具可帮助维护您的建模工件并促进不同图表中元素的可重用性.一些UML建模工具还提供复杂的建模功能,例如模型转换,报告,代码工程等. 如果您正在寻找U ...

  10. mac OS 安装 Node.js

    打开Node.js官网https://nodejs.org/zh-cn/选择你需要的版本 下载安装 安装完成 输入命令 node -v 查看版本号 输入命令 npm -v 查看版本号