一、问题现象
在项目中调用 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",把 Id、Name 等属性处理成 "id"、"name",与 PostgreSQL 中默认小写的标识符规则完全对齐。
六、验证步骤
- 重新编译 :
dotnet build,确认无编译错误 - 重启服务:运行项目
- 观察启动日志:确认启动信息和连接字符串正常输出
- 调用接口 :
GET /api/drugs?drugId=xxx - 核对 SQL 日志 :期望看到
FROM "drugs"(全小写),不再有42P01 - 接口返回 :能正确返回
Drugs对象或null
七、经验总结
这次排查的关键收获有三点:
1. PostgreSQL 的「双引号陷阱」必须牢记。 不加引号时大小写不敏感,加了引号就严格区分大小写。在团队协作中,建议统一采用「建表不加双引号,SQL 中也不要手写双引号」的约定,从源头避免此类问题。
2. SqlSugar 连接 PostgreSQL 必须显式配置 ConnMoreSettings。 尤其是 PgSqlIsAutoToLower 这个开关,它直接决定了 ORM 层面与数据库层面标识符能否对上。缺失这段配置是比写 = false 更隐蔽的坑------因为看起来好像什么都没做错。
3. 善用 ORM 的 SQL 日志输出。 SqlSugar 的 OnLogExecuting 是排查这类问题的最直接工具。如果看不到实际生成的 SQL,你可能永远猜不到是大小写在作怪。养成在开发环境开启 SQL 日志的习惯,能帮你省下大量定位时间。