ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Npgsql 2.2.4.3-net40:.NET 4.0老项目连接PostgreSQL的兼容方案与避坑指南

Npgsql 2.2.4.3-net40:.NET 4.0老项目连接PostgreSQL的兼容方案与避坑指南 简介压缩包内是 Npgsql 2.2.4.3 针对 .NET Framework 4.0 的数据库连接器版本面向需要在 C#、VB.NET 等托管代码中访问 PostgreSQL 的开发者解决 .NET 项目缺少官方 PostgreSQL 数据驱动的问题。包体共 19 个文件以 DLL 为主Npgsql.dll 提供连接、查询和事务核心能力EntityFramework 两个版本支持 ORM 映射Mono.Security.dll 负责加密与 SSL 安全通信同时附带 PDB 调试符号、XML 文档、README 与 LICENSE.txt便于调试和合规使用。整个压缩包仅 681KB轻量且不依赖额外安装适合直接复制到项目目录引用。目前已有 199 人学习浏览下载后即可获得一整套数据库访问与调试辅助文件免去自行编译或四处收集依赖的流程可在 .NET Framework 4.0 项目中快速集成 PostgreSQL。1. Npgsql-2.2.4.3-net40.zip被 .NET 4.0 卡住的老项目救星一个跑在 Windows Server 2008 R2 上的老管理系统目标框架锁死在 .NET Framework 4.0想接 PostgreSQL 却装不上新驱动Npgsql 6.x 最低要求 .NET 4.68.x 直接进 .NET 8 时代。翻遍 nuget 才发现Npgsql-2.2.4.3-net40 是 2.2 分支里最后面向 net40 编译的稳定包。它解决的是老框架下“没有官方驱动可用”的尴尬连 PostgreSQL、跑参数化 SQL、走事务全部按 ADO.NET 老规矩来。这篇东西围绕这个 zip 讲透三件事这套驱动到底能连哪些服务端、接进老工程的最小步骤、以及最容易让人翻车的五个坑。适合正在维护老代码的人也适合被强制指定 net40 环境的新项目参考。2. 先搞懂 Npgsql 2.2 的兼容边界net40 能连哪些 PostgreSQL2.1 Npgsql 2.x 的架构一个标准的 ADO.NET ProviderNpgsql 2.2 是一套非常标准的 ADO.NET 数据提供程序。它对外暴露 NpgsqlConnection、NpgsqlCommand、NpgsqlDataReader、NpgsqlParameter 这一组类底层挂接在 System.Data.Common 的抽象体系上。只要你的数据访问层已经用 IDbConnection、IDbCommand、IDataReader 做了接口隔离换驱动基本就是换工厂类与连接串的事业务代码可以不动。net40 这个标记值得先掰扯清楚。它指的是程序集的目标运行时是 .NET Framework 4.0CLR 4.0不是“装了 4.x 都能跑”的宽松说法。CLR 4 生成的程序集可以向上被 4.5、4.6、4.7、4.8 加载但不能被 3.5 或更旧的运行时加载反过来新版 Npgsql 也没法放进只装 4.0 的机器。这就是老系统的死穴Windows Server 2008 R2 自带 4.0升级运行时过不了审批于是 Npgsql-2.2.4.3-net40 成了少数几个能用的稳定选项。2.2 分支还有一个影响很多人的特点全部是同步 API。没有 OpenAsync、ExecuteReaderAsync 这些方法async/await 是 Npgsql 3.0 之后才补进来的。接手老代码时别试图把连接改成 await 调用编译期直接找不到方法真要给老框架写异步得包 Task.Run 或者把同步数据库操作丢到后台队列这属于另一种改造方案驱动本身不背这个锅。2.2 与 PostgreSQL 服务端的兼容矩阵Npgsql 2.2 出道时正对应 PostgreSQL 9.x 时期它走的 PostgreSQL 3.0 线协议和 9.0 到 9.6 配合得最顺。但协议层面 PostgreSQL 向后兼容所以拿它连 10、11、12 甚至 14握手阶段往往都能成功真正卡住你的是认证方式。服务端版本连接表现主要限制8.2 ~ 8.4可连认证基本是 md5/trust类型简单问题少9.0 ~ 9.6最佳兼容区间驱动与版本同期认证、类型映射都全10 ~ 13能握手认证可能失败默认 scram-sha-256驱动不认识14同上老协议兼容但新特性无类型映射连 10 以上的库时最常见的报错就是 authentication method 10 not supported。原因一句话PostgreSQL 10 起默认改成 scram-sha-256而 2.2.4.3 的认证协商流程里没有 SCRAM 实现服务器抛出认证方法编号 10驱动只能拒绝。解决不是换驱动能换早换了而是调整服务端认证策略把 pg_hba.conf 里对应客户端网段的 host 行认证方式从 scram-sha-256 改成 md5 或 password然后执行 pg_ctl reload 让配置生效。改配置时有个新坑PG 10 之后默认 password_encryption 是 scram-sha-256即使 pg_hba.conf 写了 md5用户口令仍是 SCRAM 格式Npgsql 依旧连不上。必须先把该用户的 password_encryption 参数临时切回 md5重新 set password 一次让口令以 md5 形式存储再配合 pg_hba.conf 的 md5 认证才能通。这个顺序反了会白折腾半小时。协议层面的兼容还要提数据类型。PostgreSQL 9.4 引入的 jsonb 在 Npgsql 2.2 里没有对应的 NpgsqlDbType.Jsonb 枚举只有 json 和 text。处理办法是把 jsonb 列当 text 读写SQL 里用列名::text 或直接 SELECT jsonb_col::text取回服务端后自己用 JsonConvert 反序列化。能跑但要清楚知道驱动不做 jsonb 层次的类型转换NULL 和空 JSON 得自己区分。2.3 和 ORM、连接池的配合边界net40 项目里能不能上 ORMDapper 可以因为它只依赖 IDbConnection 和 IDbCommand底层只要是 ADO.NET Provider 就成。我一般在老系统接 PostgreSQL 时就是 Npgsql Dapper.Contrib 的组合一个处理映射一个处理参数化 SQL改造量很小。EF6 也支持 net40但要装对应的 Npgsql.EntityFramework 插件并把配置里的 Provider 换成 Npgsql相比之下更重老项目能不上 ORM 就别上。连接池这块要注意Npgsql 2.2 的连接池是驱动进程内的不是跨应用共享。连接串里 Poolingtrue默认开时池按连接串精确匹配连接串只要多一个空格或改一个参数池就重新建一组。这会被误判成“连接数上涨没道理”排查时可以先把 Poolingfalse 跑一遍对比数据库端 pg_stat_activity 的连接数量来定位。还有一个常见理解偏差Npgsql 2.2 的 SSL 处理挂在 Mono.Security 的实现上SSLtrue 时证书链校验走的是 Mono 的 X509 链逻辑跟 .NET 自带的 ServicePointManager 不是一套。这也是为什么很多人用新版驱动没事、换到 2.2 突然报证书错误。这个问题留到第 5 章细讲。这里先记住一个结论2.2 默认不启用 SSL连接串不加 SSL 参数就是明文连接开发环境这样最省事生产要加密先去解决证书信任链。3. 在 net40 项目里跑通 Npgsql从引用 DLL 到第一条参数化查询3.1 zip 到手后怎么接进工程解压 zip 后把整个目录放到解决方案下的 lib/Npgsql-2.2.4.3 里不建议装 GAC也不建议拖进系统目录。GAC 里的程序集不受你控制机器上别的应用一覆盖你的站点可能加载到不匹配的版本放工程 bin 里版本冲突时谁的项目谁负责。2.2 分支有个特殊依赖SSL 和 X509 校验用到了 Mono.Security.dll解压包里通常会有这个 DLL。引用 Npgsql.dll 时顺手把 Mono.Security.dll 一起引用并都设置“复制本地 true”只复制 Npgsql.dll 会在运行时抛 FileNotFoundException。引用之前先确认项目目标框架。右键项目属性目标框架必须是 .NET Framework 4.0 或更高。如果项目是 net35这个包加载不了连编译都会报“不兼容”。老项目改造时很容易忽略这点VS 默认新建项目已经是 4.x 或更高直接拉 DLL 引用时 VS 会给出黄色警告但不一定阻止你编译要自己盯。3.2 连接串先放配置文件app.config 与 providerName实际工程里连接串不该写死在代码里。net40 项目通常走 app.config 或 web.config 的 connectionStrings 节点configuration connectionStrings add namePgDb connectionStringServer192.168.1.15;Port5432;Databaseappdb;User Idapp_user;Passwordapp_pass;Poolingtrue;MinPoolSize1;MaxPoolSize20;Timeout15; providerNameNpgsql / /connectionStrings /configuration然后在代码里用 System.Configuration 读出来string connStr ConfigurationManager.ConnectionStrings[PgDb].ConnectionString;逻辑说明providerName 写 Npgsql 是为了将来用 DbProviderFactories 动态拉工厂但 2.2 时代 DbProviderFactories 对 Npgsql 的注册不是自动的需要先通过 NpgsqlProviderFactory.Instance 手动创建工厂。老项目里最省心的还是直接 new NpgsqlConnection代码少、意图明确也不依赖工厂注册顺序。3.3 最小连接与查询代码Open 不报错才算开始一个控制台项目引用好 DLL 后用下面的代码做第一个连通性测试using System; using Npgsql; class Program { static void Main() { // 连接串写死在自检程序里正式项目请放到配置文件中 string connStr Server192.168.1.15;Port5432;Databaseappdb; User Idapp_user;Passwordapp_pass;Poolingfalse;; using (NpgsqlConnection conn new NpgsqlConnection(connStr)) { conn.Open(); Console.WriteLine(已连接服务端版本 conn.ServerVersion); Console.WriteLine(当前数据库 conn.Database); } } }逻辑说明using 保证连接在作用域结束时被 Close 归还给池或真正断开这里临时 Poolingfalse避免连接串写错的时候池里保留一堆错误状态。ServerVersion 是 NpgsqlConnection 在握手后从服务端拿到的版本字符串能打印出来就说明 TCP、认证、协议协商三层都通了。参数说明Server 用 IP 或主机名都行2.2 里 Host 是 Server 的别名两个写一个就好。Port 默认 5432但不少项目用私有端口这里必须和 PostgreSQL 的 listen 配置一致。Database 不填的话连接到一个与用户名同名的库容易踩“database xxx does not exist”。如果 Open 报错先分三类看连接超时多半是网络到不了 5432 端口用 telnet 或 Test-NetConnection 验证认证失败多半是用户名密码或 pg_hba.conf 的策略协议失败则回到第 2 章说的认证方式不兼容。3.4 连接成功之后先查这三条 SQLOpen 成功不等于一切都稳。接着执行三条 SQL确认驱动和服务端的会话设置是你要的using (NpgsqlConnection conn new NpgsqlConnection(connStr)) { conn.Open(); string[] checks new string[] { SELECT version();, SHOW TimeZone;, SHOW client_encoding; }; foreach (string sql in checks) { using (NpgsqlCommand cmd new NpgsqlCommand(sql, conn)) using (NpgsqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine(sql.Substring(0, Math.Min(24, sql.Length)) reader[0]); } } } }逻辑说明version() 打出服务端完整版本号TimeZone 看会话时区client_encoding 看客户端字符集。Npgsql 2.2 默认按客户端区域设置推导 client_encoding老代码最怕出现 UTF-8 和 GBK 不一致导致中文乱码。参数说明Math.Min(24, sql.Length) 只是裁短控制台输出不影响 SQL 执行。实际项目中这三条语句适合放进启动自检模块服务起来时检查一遍比等业务跑挂了再排查强。字符集这里分享一条血泪经验如果 SELECT 出来的中文变成问号或乱码先看 SHOW client_encoding 的返回值然后在连接串里显式加 EncodingUTF8 或 EncodingGBK具体取决于你服务端存储的编码。Npgsql 2.2 对编码的推断逻辑有些玄学显式指定是最稳的做法。4. 连接串与参数化net40 下最容易写错的几个细节4.1 连接串参数逐项拆解Npgsql 2.2 的连接串是分号分隔的 key-value大小写不敏感但 key 拼错会被当成未知项忽略而不是报错。下面这张表列出我每次接手 Npgsql 项目必查的参数参数默认作用与建议Server / Host无数据库地址支持 IP、主机名Port5432注意服务端 postgresql.conf 的 port改过必须配准Database与用户名同名建议显式写避免 pg_hba.conf 里 database 字段匹配错User Id无对应 PostgreSQL 角色名Password无建议用加密配置保存不要硬编码Poolingtrue常驻进程建议开着短命令行工具建议关闭MinPoolSize0进程启动时预热连接避免高峰抢建连接MaxPoolSize20高并发项目必须调大默认 20 很容易不够Timeout15Open 的连接超时秒数网络慢时要调大CommandTimeout20命令执行超时独立于连接超时SSLfalse2.2 默认明文开 SSL 前先解决证书链KeepAlive0TCP 保活秒数跨防火墙的库建议设 1每个参数都有故事。Timeout 和 CommandTimeout 是两个东西Timeout 管连接建立CommandTimeout 管一条 SQL 执行。老项目把 CommandTimeout 调成 300 秒后还是连不上还以为是命令慢实际是 Open 那一步就卡了 30 秒后放弃排查半天最后是防火墙丢包。这个坑很常见。KeepAlive 在 2.2 里走的是 TCP keepalive不设的话数据库两侧的空闲连接可能被交换机或防火墙静默干掉。进程还在池里的连接状态还是 Idle第一次 Execute 直接报 IOException: Connection has been closed。预防办法是连接串写 KeepAlive1付出很小的检测开销换掉半夜“第一个请求必挂”的毛病。4.2 参数化查询与 NpgsqlDbType类型推断不靠谱显式声明才稳强烈建议每一条 NpgsqlParameter 都显式声明 NpgsqlDbTypeusing (NpgsqlConnection conn new NpgsqlConnection(connStr)) { conn.Open(); const string sql SELECT id, name, created_at FROM users WHERE status status AND created_at since;; using (NpgsqlCommand cmd new NpgsqlCommand(sql, conn)) { // 显式声明类型让驱动走整数和 timestamp 的二进制协议 cmd.Parameters.Add(new NpgsqlParameter(status, NpgsqlDbType.Integer) { Value 1 }); cmd.Parameters.Add(new NpgsqlParameter(since, NpgsqlDbType.Timestamp) { Value DateTime.Now.AddDays(-7) }); using (NpgsqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine({0} - {1} - {2}, reader[id], reader[name], reader[created_at]); } } } }逻辑说明status、since 是参数占位符Npgsql 2.2 里占位符用 前缀和 SQL Server 习惯一致。参数化能杜绝 SQL 注入也避免因字符串拼接产生隐式类型转换。关键在显式声明类型2.2 的类型推断没有 3.x 那么智能字符串参数默认按 text 处理遇到 status status 这种整型列对比PostgreSQL 执行器会尝试把参数 cast 成 integer索引可能用不上退化成全表扫描。参数说明NpgsqlParameter 构造参数第一个是参数名第二个是 NpgsqlDbType 枚举。2.2 的枚举名和新版有区别整数是 Integer带时区的时间戳是 TimestampTZ新版本叫 TimestampTz大整数是 BigInt。升级版本时要注意枚举名大小写差一个字母代码就编译不过。2.2 的 NpgsqlDbType 与服务端类型大致对照如下NpgsqlDbType (2.2)PostgreSQL 类型说明Integerinteger32 位整型BigIntbigint64 位整型对应 longTexttext变长文本Varcharvarchar(n)需要配合 Size 属性Numericnumeric精确小数对应 decimalTimestamptimestamp without time zone注意与客户端 DateTimeKind 配合TimestampTZtimestamp with time zone2.2 的枚举写法与新版不同Booleanboolean.NET boolByteabytea固定二进制4.3 事务与批量写入BeginTransaction 别省略using (NpgsqlConnection conn new NpgsqlConnection(connStr)) { conn.Open(); using (NpgsqlTransaction tx conn.BeginTransaction()) { const string insertSql INSERT INTO logs(level, message, ts) VALUES (level, message, ts);; using (NpgsqlCommand cmd new NpgsqlCommand(insertSql, conn, tx)) { cmd.Parameters.Add(new NpgsqlParameter(level, NpgsqlDbType.Integer)); cmd.Parameters.Add(new NpgsqlParameter(message, NpgsqlDbType.Text)); cmd.Parameters.Add(new NpgsqlParameter(ts, NpgsqlDbType.Timestamp)); for (int i 0; i 1000; i) { cmd.Parameters[0].Value i % 3; cmd.Parameters[1].Value log line i; cmd.Parameters[2].Value DateTime.Now; cmd.ExecuteNonQuery(); } } tx.Commit(); } }逻辑说明没有 BeginTransaction 时Npgsql 2.2 默认 autocommit每条 INSERT 都是一次独立事务写入1000 条就是 1000 次 fsync性能差一个数量级。包进事务后最后一次 Commit 才落盘。这是老系统从 MySQL 迁到 PostgreSQL 后最明显的性能变化点。参数说明NpgsqlCommand 构造函数的第三个参数传事务对象确保命令在事务上下文中执行。循环里每次只改 Parameters 集合里项的 Value不再新建 NpgsqlParameter减少对象分配也让驱动有机会复用参数元数据。2.2 没有批量插入 API这个写法是性能与代码量之间的平衡。想要更快只能上 COPY但 COPY 在 2.2 里没有公开的专门接口得靠 NpgsqlCommand 执行 COPY FROM STDIN 再手工写流复杂度高非必要不建议。5. Npgsql 2.2.4.3-net40 避坑与排查五条常见的翻车记录5.1 高版本 PostgreSQL 认证失败authentication method 10现象Open() 抛异常错误信息里能看到 authentication method 10 not supported或者服务端 requested unsupported authentication method。原因PG 10 的默认 pg_hba.conf 使用 scram-sha-256认证协商码是 10Npgsql 2.2.4.3 实现的是 md5 和 password 两种老式认证不认 SCRAM。解决按第 2.2 节的顺序处理 pg_hba.conf 和 password_encryption改完 reload 再重试。注意这条解决路径只适用于你能控制服务端配置的场景如果数据库是云厂商托管的很多厂商不允许切认证方式那 net40 Npgsql 2.2 这条路基本走不通只能换驱动或升级运行时这是需要提前做的底线判断。5.2 SSL 自签证书校验翻车Mono.Security 的 X509 链现象连接串加了 SSLtrueOpen 偶尔成功执行查询时抛 System.Security.Cryptography.CryptographicException 或 SSL connection has been closed。原因Npgsql 2.2 的 SSL 校验依赖 Mono.Security 的 X509 链检查。自签证书不在系统受信任根里自然过不去。而且 2.2 没有现代 Npgsql 的 SslModeRequire 加信任一切证书的开关你没法在连接串里直接跳过证书验证。解决开发环境直接不开 SSL纯内网明文跑业务生产环境用内网 CA 给 PostgreSQL 签证书把根 CA 导入 Windows 的“受信任的根证书颁发机构”存储。注意导入后要重启应用进程因为证书存储只在进程启动时读一次。很多人导入证书后不重启继续报错以为是玄学其实是缓存。5.3 时间戳差 8 小时与 Numeric 精度现象一SELECT created_at 返回的时间比数据库存储的时间差 8 小时。现象二numeric 列读出 0.30000000000000004 这种值。原因一Npgsql 2.2 的 TimestampTZ 反序列化时会按客户端本地时区换算。如果数据库存的是 UTC客户端机器时区是东八区读出时驱动把时间从 UTC 转成本地看着像差 8 小时。原因二numeric 是 PostgreSQL 的任意精度定点数老驱动按 double 途经转换时引入二进制浮点误差。解决应用层统一约定数据库全用 timestamp without time zone 存业务时间读取后在 .NET 侧把 DateTime 的 Kind 定为 Utc显示时再 ToLocalTime()。numeric 列读取时显式映射到 decimal避免驱动推断成 double。如果历史列改不动SQL 上可以 cast(numeric_col as text) 包一层取回来自己解析字符串精度损失变成零。5.4 同一 SQL 执行几千次不 Prepare 就慢现象一条 UPDATE 在主键条件下执行 3000 次数据库 CPU 高但单条也就几十毫秒总时长爆炸。原因PostgreSQL 对每条未准备的 SQL 都要做 parse planNpgsql 2.2 不会自动给参数化 SQL 做服务端准备必须显式调用。很多从新版驱动降级下来的人会漏这一步因为 3.x 之后有自动准备策略。解决using (NpgsqlCommand cmd new NpgsqlCommand( UPDATE users SET last_login t WHERE id id;, conn)) { cmd.Parameters.Add(new NpgsqlParameter(t, NpgsqlDbType.Timestamp)); cmd.Parameters.Add(new NpgsqlParameter(id, NpgsqlDbType.Integer)); cmd.Prepare(); // 关键先让服务端准备好执行计划 foreach (int userId in userIds) { cmd.Parameters[0].Value DateTime.UtcNow; cmd.Parameters[1].Value userId; cmd.ExecuteNonQuery(); } }逻辑说明cmd.Prepare() 会向服务端发 PREPARE 语句后续执行走已命名计划。循环里只改参数 Value不再改 CommandText计划可以复用。注意 Prepare 后不能再改 SQL 文本改了要重新 Prepare。5.5 连接池的空闲连接变成僵尸现象应用跑了一夜早晨第一个请求超时之后自动恢复。日志里是 SocketException 或 IOException 套着 Timeout 报错。原因数据库端或中间防火墙主动关闭了长时间空闲的 TCP 连接Npgsql 2.2 池里的连接还处于 Idle应用取出来直接用发现底层 socket 已经死了。解决连接串加 KeepAlive1让 TCP 层周期性探测同时在代码里为第一次异常做一次重试try { // 业务查询 } catch (System.IO.IOException) { // 池里可能是坏连接清空后再重试一次 NpgsqlConnection.ClearAllPools(); // 重新执行业务查询 }逻辑说明ClearAllPools 会把当前进程所有池里的物理连接断开下次 Open 重新建。这是一颗有副作用的后悔药只建议在捕获到 IO 或连接类异常时用不要当成每次查询的常规路径。它解决的是“池里标记是好连接、实际是坏 socket”的情形。6. 收尾验证给 net40 项目做一次十分钟驱动体检6.1 连接池自检脚本把下面代码塞进启动模块或者写成一个独立的 Batch 工具线上出问题时跑一次NpgsqlConnection.ClearAllPools(); string connStr Server192.168.1.15;Port5432;Databaseappdb;User Idapp_user;Passwordapp_pass;Poolingtrue;; using (NpgsqlConnection conn new NpgsqlConnection(connStr)) { conn.Open(); using (NpgsqlCommand cmd new NpgsqlCommand( SELECT count(*) FROM pg_stat_activity WHERE datname current_database();, conn)) { object total cmd.ExecuteScalar(); Console.WriteLine(当前数据库活动连接数 total); } }逻辑说明ClearAllPools 先把当前进程所有池物理连接拆掉再重新 Open确保拿到的是新连接而不是僵尸连接。第二段 SQL 查 pg_stat_activity 可以对比应用侧连接数估算是否发生连接泄漏。连上后再执行 3.4 里的三条自检 SQL输出期望的时区和编码整个体检就完成了。6.2 我养成的收尾习惯每次迁完老项目我会顺手在运维脚本里加一条 SELECT version(); 的监控每天凌晨跑一次同时把 pg_stat_activity 里连接数与 MaxPoolSize 乘应用实例数做对照。这么做不是为了好看而是 Npgsql 2.2 一旦连接池异常表现永远是“半夜第一个请求超时”没有监控日志你根本不知道它几点开始坏的。老框架配老驱动能撑多久不翻车靠的不是代码写得漂亮而是把检查点前置到故障发生之前。这个习惯帮我省了不少凌晨三点的电话希望帮到你。本文还有配套的精品资源点击获取
返回列表