SqlSugar 连接 PostgreSQL 报错 42P01: relation does not exist 的排查与修复

一、问题现象

在项目中调用 GET /api/drugs?drugId=xxx 接口时,服务端抛出 500 异常,日志关键信息如下:

arduino 复制代码
2026-06-15 17:09:37.599 +08:00 [ERR] An unhandled exception has occurred while executing the request.
Npgsql.PostgresException (0x80004005): 42P01: relation "drugs" does not exist
   at SqlSugar.QueryableProvider`1.FirstAsync()
   at ...Services.DrugService.GetDrugByIdAsync(String drugId)
   at ...Controllers.DrugController.GetDrugByIdAsync(String drugId)

报错代码 42P01 在 PostgreSQL 中代表「关系不存在」,通常是指查询中引用的表(或视图)在当前数据库内找不到。

二、从 SQL 日志中找线索

打开 Program.cs 查看 SqlSugar 注册代码,注意到配置了 Aop.OnLogExecuting 事件,所以日志中完整输出了生成的 SQL:

sql 复制代码
SQL: SELECT "id","name","normalizedname",... FROM "drugs" WHERE ( "id" = @Id0 ) LIMIT 1 offset 0

与此同时,Drugs 实体类定义在 MyCommon/Entities/Drugs.cs 中:

csharp 复制代码
[SugarTable("Drugs")]
public partial class Drugs
{
    [SugarColumn(IsPrimaryKey = true)]
    public string Id { get; set; } = null!;
    public string Name { get; set; } = null!;
    // ...
}

把上面两段信息放在一起,问题就浮出水面了:

代码中的名称 SQL 中实际出现的名称
Drugs(类名 + SugarTable) "drugs"(双引号包裹,全小写)
Id(属性名) "id"(双引号包裹,全小写)
Name(属性名) "name"(双引号包裹,全小写)

SqlSugar 生成的表名和列名与实体类中定义的完全对不上------这就是 42P01 的直接原因。

三、PostgreSQL 的大小写敏感规则

在继续分析之前,必须先明确一条 PostgreSQL 的核心行为规则:

不加双引号的标识符会被自动折叠为小写,因此大小写不敏感;加上双引号 "Name" 包裹的标识符会严格区分大小写。

举例来说:

sql 复制代码
CREATE TABLE Drugs (...);    -- 实际存进系统表的是 drugs(小写)
CREATE TABLE "Drugs" (...);  -- 实际存进系统表的是 Drugs(保留大写)

SELECT * FROM Drugs;         -- 等价于 SELECT * FROM drugs; 能命中
SELECT * FROM "Drugs";       -- 只能命中保留大写的表 "Drugs"
SELECT * FROM "drugs";       -- 只能命中保留小写的表 "drugs"

Drugs 实体标注了 [SugarTable("Drugs")],说明期望查询的表叫 Drugs;但 SqlSugar 实际生成的 SQL 却变成了 "drugs"。如果数据库中的表名是按 PostgreSQL 默认行为建的(即小写 drugs),理论上应该命中;如果按保留大写建的("Drugs"),那就匹配不上。

无论数据库中的表是哪一种,让 SqlSugar 按 PostgreSQL 的默认小写规则去生成 SQL ,是兼容性最好的做法。而控制 SqlSugar 这一行为的开关,正是 ConnMoreSettings 中的 PgSqlIsAutoToLower

四、真正的根因:缺少 ConnMoreSettings 配置

回到项目中 Program.cs,最初注册 SqlSugar 的代码是这样的(修改前):

csharp 复制代码
builder.Services.AddScoped<ISqlSugarClient>(options =>
{
    ISqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
    {
        ConnectionString = configuration.GetConnectionString("DefaultConnection"),
        DbType = DbType.PostgreSQL,
        IsAutoCloseConnection = true,
        // ← 这里没有配置 ConnMoreSettings
    }, db => { db.Aop.OnLogExecuting = (sql, pars) => { Log.Information($"SQL: {sql} {pars}"); }; });
    return sugarClient;
});

没有配置 MoreSettings ,意味着 SqlSugar 对 PostgreSQL 的大小写处理使用其内部默认逻辑,生成的表名/列名与数据库中实际的表名列名不匹配 ,于是报 42P01

注意:我在第一次排查时曾误以为代码里已有 PgSqlIsAutoToLower = false,后经指正才明确------原始代码里根本没有这段配置PgSqlIsAutoToLower = false 是用户后来手动加上的「尝试性修复」,但它同样不对(显式禁止小写转换依然无法匹配数据库中的小写表名)。

五、修复方案

ConnectionConfig 中添加 MoreSettings,并将三个 PgSql*ToLower 开关设为 true

csharp 复制代码
builder.Services.AddScoped<ISqlSugarClient>(options =>
{
    ISqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
    {
        ConnectionString = configuration.GetConnectionString("DefaultConnection"),
        DbType = DbType.PostgreSQL,
        IsAutoCloseConnection = true,
        MoreSettings = new ConnMoreSettings()
        {
            PgSqlIsAutoToLower = true,
            PgSqlIsAutoToLowerSchema = true,
            PgSqlIsAutoToLowerCodeFirst = true,
        }
    }, db => { db.Aop.OnLogExecuting = (sql, pars) => { Log.Information($"SQL: {sql} {pars}"); }; });
    return sugarClient;
});

三个开关的作用:

配置项 作用
PgSqlIsAutoToLower 普通查询时,是否将表名、列名自动转为小写
PgSqlIsAutoToLowerSchema 模式(Schema)名是否自动转小写
PgSqlIsAutoToLowerCodeFirst Code First 建表/迁移时是否使用小写

设置为 true 后,SqlSugar 会把 [SugarTable("Drugs")] 处理成 "drugs",把 IdName 等属性处理成 "id""name",与 PostgreSQL 中默认小写的标识符规则完全对齐。

六、验证步骤

  1. 重新编译dotnet build,确认无编译错误
  2. 重启服务:运行项目
  3. 观察启动日志:确认启动信息和连接字符串正常输出
  4. 调用接口GET /api/drugs?drugId=xxx
  5. 核对 SQL 日志 :期望看到 FROM "drugs"(全小写),不再有 42P01
  6. 接口返回 :能正确返回 Drugs 对象或 null

七、经验总结

这次排查的关键收获有三点:

1. PostgreSQL 的「双引号陷阱」必须牢记。 不加引号时大小写不敏感,加了引号就严格区分大小写。在团队协作中,建议统一采用「建表不加双引号,SQL 中也不要手写双引号」的约定,从源头避免此类问题。

2. SqlSugar 连接 PostgreSQL 必须显式配置 ConnMoreSettings 尤其是 PgSqlIsAutoToLower 这个开关,它直接决定了 ORM 层面与数据库层面标识符能否对上。缺失这段配置是比写 = false 更隐蔽的坑------因为看起来好像什么都没做错。

3. 善用 ORM 的 SQL 日志输出。 SqlSugar 的 OnLogExecuting 是排查这类问题的最直接工具。如果看不到实际生成的 SQL,你可能永远猜不到是大小写在作怪。养成在开发环境开启 SQL 日志的习惯,能帮你省下大量定位时间。

相关推荐
2601_961875242 小时前
法考考试时间安排及科目|时间表|资料已整理
开发语言·c#·inverted-index·suffix-tree·sstable·r-tree·lsm-tree
ServBay2 小时前
你跟高级 C# 工程师的区别,就是这8个开发技巧
后端·c#·.net
2601_961875247 小时前
法考备考计划表|学习计划|资料已整理
java·开发语言·学习·eclipse·tomcat·c#·hibernate
Y学院8 小时前
C#游戏脚本开发全流程(Unity通用完整版)
游戏·unity·c#
淡海水10 小时前
38-Hybrid生态-LeanCLR总览
unity·架构·c#·热更新·clr·hybrid·leanclr
iCxhust11 小时前
C# 生成命令行程序 将hex格式烧录程序转换成bin烧录格式
开发语言·汇编·单片机·嵌入式硬件·c#·微机原理
xiaoshuaishuai811 小时前
C# 封装与继承
开发语言·c#
FL162386312911 小时前
基于C#winform使用纯opencv部署ppocrv5和ppocrv6的onnx模型进行OCR文件检测识别
opencv·c#·ocr
小满Autumn17 小时前
log4net 日志框架 — 从配置到实战速查手册
笔记·c#·.net·wpf·上位机·log4net