Postgres ENUM类型首次运行时无法被Dapper识别的问题求助
Postgres ENUM 同会话创建后查询报错的解决方案
问题原因
Npgsql在连接初始化时会获取并缓存数据库的元数据(包括所有类型信息)。当你在同一会话中创建新的ENUM类型后,Npgsql本地的缓存不会自动刷新,导致后续查询时无法识别这个刚创建的ENUM,从而抛出类型未找到的错误。第二次运行时,连接初始化时ENUM已经存在,缓存里有对应的类型信息,所以查询正常。
解决方案
方法1:手动刷新类型缓存
在创建ENUM和表之后,调用NpgsqlConnection的ReloadTypesAsync方法,强制刷新本地的类型缓存,让Npgsql获取最新的数据库类型信息。
修改后的代码示例:
using Dapper; using Npgsql; var connection = new NpgsqlConnection("Host=127.0.0.1;Port=5432;User ID=postgres;Password=password;Database=messaging;Include Error Detail=true;"); await connection.OpenAsync(); var tableCommand = new NpgsqlCommand() { Connection = connection, CommandText = """ CREATE TYPE message_type AS ENUM ('sms', 'email'); CREATE TABLE message ( id SERIAL PRIMARY KEY, type message_type NOT NULL ); """ }; await tableCommand.ExecuteNonQueryAsync(); // 关键步骤:刷新数据库类型缓存 await connection.ReloadTypesAsync(); var queryCommand = new NpgsqlCommand() { Connection = connection, CommandText = """ SELECT id, type FROM message; """ }; var reader = await queryCommand.ExecuteReaderAsync(); var parser = reader.GetRowParser<Message>(); class Message { public int Id { get; set; } public string? Type { get; set; } }
方法2:拆分连接执行操作
创建ENUM和表用一个独立连接,查询用另一个新连接。新连接初始化时会自动加载最新的数据库元数据,自然能识别已创建的ENUM。
代码示例:
using Dapper; using Npgsql; // 第一个连接:负责创建类型和表 using (var createConnection = new NpgsqlConnection("Host=127.0.0.1;Port=5432;User ID=postgres;Password=password;Database=messaging;Include Error Detail=true;")) { await createConnection.OpenAsync(); var tableCommand = new NpgsqlCommand() { Connection = createConnection, CommandText = """ CREATE TYPE message_type AS ENUM ('sms', 'email'); CREATE TABLE message ( id SERIAL PRIMARY KEY, type message_type NOT NULL ); """ }; await tableCommand.ExecuteNonQueryAsync(); } // 第二个连接:执行查询操作 using (var queryConnection = new NpgsqlConnection("Host=127.0.0.1;Port=5432;User ID=postgres;Password=password;Database=messaging;Include Error Detail=true;")) { await queryConnection.OpenAsync(); var queryCommand = new NpgsqlCommand() { Connection = queryConnection, CommandText = """ SELECT id, type FROM message; """ }; var reader = await queryCommand.ExecuteReaderAsync(); var parser = reader.GetRowParser<Message>(); } class Message { public int Id { get; set; } public string? Type { get; set; } }
方法3:提前注册ENUM类型
如果提前知道ENUM的定义,可以在打开连接前,用Npgsql的全局类型映射器注册对应的枚举类型,这样即使是同会话创建,也能正确识别。
代码示例:
using Dapper; using Npgsql; // 提前注册ENUM与C#枚举的映射 NpgsqlConnection.GlobalTypeMapper.MapEnum<MessageType>(); var connection = new NpgsqlConnection("Host=127.0.0.1;Port=5432;User ID=postgres;Password=password;Database=messaging;Include Error Detail=true;"); await connection.OpenAsync(); var tableCommand = new NpgsqlCommand() { Connection = connection, CommandText = """ CREATE TYPE message_type AS ENUM ('sms', 'email'); CREATE TABLE message ( id SERIAL PRIMARY KEY, type message_type NOT NULL ); """ }; await tableCommand.ExecuteNonQueryAsync(); var queryCommand = new NpgsqlCommand() { Connection = connection, CommandText = """ SELECT id, type FROM message; """ }; var reader = await queryCommand.ExecuteReaderAsync(); var parser = reader.GetRowParser<Message>(); // 定义与Postgres ENUM对应的C#枚举 public enum MessageType { sms, email } class Message { public int Id { get; set; } public MessageType Type { get; set; } }
适用场景说明
- 方法1适合需要在同一会话中完成创建和查询的场景,操作简单直接。
- 方法2适合迁移脚本等不需要保持会话连续性的场景,逻辑清晰。
- 方法3适合提前明确ENUM定义的场景,能获得更强的类型安全性。
内容的提问来源于stack exchange,提问作者Rick de Water
相关产品推荐
相关产品推荐

