使用Npgsql创建PostgreSQL枚举类型后二进制导入失败,如何解决?
在.NET项目中使用Npgsql创建PostgreSQL枚举类型后执行二进制导入操作时,抛出System.ArgumentException,提示"A PostgreSQL type with the name mood was not found in the database",但数据库中已成功创建该枚举类型。
项目文件
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Npgsql" Version="7.0.2" /> </ItemGroup> </Project>
源码文件
using Npgsql; var connectionString = "Host=localhost;" + "Port=5432;" + "Username=postgres;" + "Password=postgres;" + "Database=test_db;"; using var connection = new NpgsqlConnection(connectionString); connection.Open(); var cmd = connection.CreateCommand(); cmd.CommandText = "create type mood as enum ('happy', 'sad');" + "create table people (age integer, cmood mood);"; cmd.ExecuteNonQuery(); var copyCmd = "copy people (age, cmood)" + "from stdin (format binary)"; using var writer = connection.BeginBinaryImport(copyCmd); writer.StartRow(); writer.Write(12); writer.Write(Mood.Happy); writer.Complete(); enum Mood { Null = 0, Sad = 1, Happy = 2 }
问题详情
本地PostgreSQL服务已运行,空数据库test_db已创建。执行程序时触发上述异常,但通过命令行查询数据库确认mood枚举类型已成功创建。
Npgsql在建立连接后会缓存数据库的元数据(包括枚举类型),而代码是在连接打开后才创建的mood枚举类型,此时Npgsql的本地元数据缓存中并没有这个新类型的信息。二进制导入(BeginBinaryImport)依赖元数据来匹配类型编码规则,缓存未更新就会导致无法识别新枚举类型,抛出异常。
另外,当前.NET枚举值的顺序和PostgreSQL枚举定义顺序不匹配,会导致导入后数据值错误(比如.NET的Happy=2会对应PostgreSQL中第二个枚举值sad)。
1. 刷新元数据缓存
在创建枚举和表之后,调用connection.ReloadTypes()刷新Npgsql的元数据缓存,让它感知到新创建的mood类型:
cmd.ExecuteNonQuery(); // 刷新元数据缓存 connection.ReloadTypes(); var copyCmd = "copy people (age, cmood)" + "from stdin (format binary)"; // 后续二进制导入代码不变
2. 修正.NET枚举与PostgreSQL枚举的映射
确保.NET枚举与PostgreSQL枚举的定义顺序或名称对应一致:
方式一:匹配枚举顺序(推荐)
PostgreSQL中mood的顺序是'happy'(第一个)、'sad'(第二个),对应.NET枚举的索引值:
enum Mood { Happy = 0, Sad = 1 }
方式二:使用特性显式指定映射
如果不想调整枚举值,可通过PgName特性绑定枚举值与PostgreSQL枚举名称:
using NpgsqlTypes; enum Mood { [PgName(null)] Null = 0, [PgName("sad")] Sad = 1, [PgName("happy")] Happy = 2 }
3. 重新打开连接
如果不想调用ReloadTypes(),可关闭当前连接后重新打开,新连接会重新加载最新的元数据:
cmd.ExecuteNonQuery(); // 关闭并重新打开连接 connection.Close(); connection.Open(); var copyCmd = "copy people (age, cmood)" + "from stdin (format binary)"; // 后续二进制导入代码不变
内容的提问来源于stack exchange,提问作者fra_pero

