在ef core中使用postgres数据库的全文检索功能实战
起源
之前做的很多项目都使用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数据库的全文检索功能实战的更多相关文章
- 在ef core中使用postgres数据库的全文检索功能实战之中文支持
前言 有关通用的postgres数据库全文检索在ef core中的使用方法,参见我的上一篇文章. 本文实践了zhparser中文插件进行全文检索. 准备工作 安装插件,最方便的方法是直接使用安装好插件 ...
- EF Core中如何设置数据库表自己与自己的多对多关系
本文的代码基于.NET Core 3.0和EF Core 3.0 有时候在数据库设计中,一个表自己会和自己是多对多关系. 在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句 ...
- EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的
我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...
- EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况
使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...
- EF Core中怎么实现自动更新实体的属性值到数据库
我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...
- [小技巧]EF Core中如何获取上下文中操作过的实体
原文地址:https://www.cnblogs.com/lwqlun/p/10576443.html 作者:Lamond Lu 源代码:https://github.com/lamondlu/EFC ...
- EF Core中避免贫血模型的三种行之有效的方法(翻译)
Paul Hiles: 3 ways to avoid an anemic domain model in EF Core 1.引言 在使用ORM中(比如Entity Framework)贫血领域模型 ...
- EF Core中DbContext可以被Dispose多次
我们知道,在EF Core中DbContext用完后要记得调用Dispose方法释放资源.但是其实DbContext可以多次调用Dispose方法,虽然只有第一次Dispose会起作用,但是DbCon ...
- 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)
原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ...
随机推荐
- C# WCF之用接口创建服务契约、部署及客户端连接
服务契约描述了暴露给外部的类型(接口或类).服务所支持的操作.使用的消息交换模式和消息的格式.每个WCF服务必须实现至少一个服务契约.使用服务契约必须要引用命名空间System.ServiceMode ...
- webpack之Loader
我们知道webpack的优点之一就是专注于处理模块化的项目,能做到开箱即用,但同时这也是webpack的缺点,只能用于模块化开发的项目,例如:Vue,React,Angular.Webpack在进行打 ...
- 学习Python爬虫的4幅思维导图
这次给大家带来的是4 幅思维导图,梳理了 Python 爬虫部分核心知识点:网络基础知识,Requests,BeautifulSoup,urllib 和 Scrapy 爬虫框架. 爬虫是一个非常有趣的 ...
- 白话理解https
为什么需要加密? 因为http的内容是明文传输的,传输过程有可能被劫持或被篡改(中间人攻击),如何解决? 当然是加密.最简单的方式就是对称加密(快). 对称机密 就是一个密钥,可以理解为一把钥匙,我们 ...
- SpringCloud-Bus 消息总线
概述 基本介绍 Spring Cloud Bus 目前支持两种消息代理:RabbitMQ.Kafka Spring Cloud Config 配合 Spring Cloud Bus 使用可以实现配置的 ...
- INDIRECT函数实现动态图表的跨数据抓取
涉及函数: indirect函数:通常有两种用法.直接指定单元格地址和隐式指定单元格地址.直接指定:=indirect("A4"),则会返回A4单元格所显示的内容.参数给定的既是字 ...
- PE文件学习(2)导入表导出表
转自:evil.eagle https://blog.csdn.net/evileagle/article/details/12176797 导出表是用来描述模块中的导出函数的结构,如果一个模块导出了 ...
- [Linux] 检查是否已有进程在运行
出处:sblim-sfcb-1.4.9 / sfcBroker.c int process_is_running() { #define STRBUF_LEN 512 #define BUF_LEN ...
- 解决Idea配置文件不显示中文的问题
1.首先我们的IDEA文件编码一般都修改为utf-8(setting-->file encodings--->Global Encoding 和 Project Encoding 都设置为 ...
- Android xUtils3.0使用手册(一)- 基础功能使用
xUtils3 其功能不得不说,简化了很多的开发步骤,可以说是非常好的开发工具,但是苦于没有完整的使用手册,下面是使用中的一些总结,不断完善. xUtils 版本 3.3.36 jar包下载地址 ht ...