简介:面向.NET Framework 4.0平台的PostgreSQL数据库连接器,供使用C#、VB.NET等语言并通过ADO.NET接口进行数据操作的开发者使用。压缩包包含Npgsql.dll主库、Entity Framework及Legacy支持库、Mono.Security.dll安全组件,并提供多语言资源文件,可满足连接、查询、事务、ORM映射及加密通信等场景。整体共19个文件,以dll库为主,配以pdb调试符号、xml接口文档、config配置及文本说明,压缩包大小仅681KB,十分轻量,部署方便。已有199人学习下载,适合中小型项目快速集成,也适用于维护基于.NET Framework 4.0的旧系统。该版本内含README.md配置指引和LICENSE.txt开源协议,便于理解使用条款;pdb文件配合Visual Studio可实现源码级调试,xml文档辅助快速查阅API。打包内容完整,省去自行编译与兼容性验证的麻烦,是.NET Framework 4.0环境下连接PostgreSQL的实用选择。
1. Npgsql-2.2.4.3-net40:一台只剩 .NET 4.0 的老服务器,绕不开它的场景
收到一个叫 Npgsql-2.2.4.3-net40.zip 的包,多半不是在装新系统,而是在救一台跑着 .NET 4.0 的旧机器。这个包是 PostgreSQL 生态里历史悠久的 .NET 数据驱动 Npgsql 的 2.2.4.3 版本,net40 后缀说明它面向 .NET Framework 4.0(CLR 4.0)编译。它的作用很直接:让老 C#、VB.NET 程序用 ADO.NET 的 Connection、Command、DataReader 直接读写 PostgreSQL,不装数据库客户端,也不依赖中间件。需要它的人基本是同一类:数据库从 Oracle、MSSQL 迁到 PostgreSQL,但业务程序被历史包袱锁死在 .NET 4.0 上。新版 Npgsql 动不动要求 .NET 4.6.2 甚至更高,卡在 4.0 上的项目能用的备选不多,这个老包就成了最顺手的一条路。别以为新驱动一定比老驱动好用,在受限环境下,老版本带来的确定性反而最宝贵。
2. 把 zip 变成可用引用:net40 目录、dll 引用与目标框架的匹配
拿到这个 zip,先别急着在 Visual Studio 里双击或解压。老版本 Npgsql 的发布包习惯把多个目标框架的编译产物放一起,解压后很可能看见 net20、net35、net40 这样的子目录,有的还带 net45。搞清楚这些目录的含义再动手,能少走很多弯路。
2.1 解压后的常见形态:net20、net35、net40 这些目录在说什么
Npgsql 用同一套源码为不同版本 .NET 编译,net40 目录下的程序集引用的是 .NET Framework 4.0 的运行时和基础类库,由 Visual Studio 2010 及之后的编译器生成。这里有个容易混淆的点:net40 程序集能运行在后续的 .NET Framework 4.5、4.6、4.8 上,因为 Framework 是向后兼容的;反过来,net35 的程序集想跑在 4.0 进程里通常也可以,但官方发布时把它们单独分目录,就是提醒你别拿错。
真实的 zip 里不会只有 Npgsql.dll 一个文件,2.2.x 时代它往往还带着依赖的辅助程序集,比如处理 SSL 连接或安全相关逻辑时需要用到的部分。很多人只把主 dll 拷走,剩下的一律不理会,结果程序跑起来后在某个功能点突然抛程序集加载异常,那时候再回想是哪个 dll 没到位,排查成本就高了。我一般会把整个 net40 目录原样复制到项目的第三方库目录,再按目录去引用,而不是只挑一个 Npgsql.dll。
2.2 为什么我不用 NuGet 而用 zip 引用:离线环境下的可控性
你可能想问:为什么不用 Package Manager Console 敲一条 Install-Package Npgsql -Version 2.2.4.3?机器能连外网的话,NuGet 确实更方便。但真实用到这个包的环境,多数是连不了外网或者被安全策略锁住的业务内网,不允许访问 NuGet 源,也不让随便装新软件。zip 方式在内网里是最老实的做法:拷进去就能用,不动全局程序集缓存,不写系统配置,卸载时直接删目录,干净利落。
手动引用的步骤很固定:
第一步,在解决方案里建一个第三方依赖目录,比如叫 libs/Npgsql/net40。第二步,把解压出的 net40 目录里所有文件复制进去。第三步,在 VS 里右键项目引用,选“添加引用”,切到“浏览”页签,定位到刚才复制的位置,选 Npgsql.dll。第四步,把该引用的“复制本地”属性设为 true。这个属性非常关键,它决定编译后 dll 会不会自动进到 bin 目录;发布时少带一个文件,生产环境就会用报错来提醒你。
2.3 平台位数与目标框架:net40 不是“随便跑”的万能包
net40 描述的是 .NET 运行时版本,不是 CPU 平台。老项目里默认配置通常是 AnyCPU,但很多实际部署环境已经被外部组件限死了位数,比如某个老 COM 控件只有 32 位版本,IIS 应用池被迫开了“启用 32 位应用程序”。这时候如果连接串和代码都没问题,驱动却在 Open 时抛 BadImageFormatException,很多人第一反应是 Npgsql.dll 损坏,重新解压、重新引用,折腾几小时后发现完全没用。
正确做法是先把进程的位数问题确定下来:项目配置管理器的活动解决方案平台是 x86 还是 x64,部署的 IIS 应用池是否启用了 32 位,Windows 服务有没有设置平台标记。Npgsql.dll 的位数跟着进程走,不是独立决定因素。遇到 BadImageFormatException,先查平台匹配,再查文件完整性,顺序反了就是浪费时间。
3. 连上 PostgreSQL:连接字符串、参数化查询与最小可跑示例
引用配好后,下一步是让驱动真正把网络打通。Npgsql 从 2.x 到 4.x,命令对象的使用方式变化不大,老代码迁到新驱动时改的主要是连接字符串和个别类型映射。这里先把 2.2.4.3 的常见连接参数讲清楚,再给一段能立即验证连通性的代码。
3.1 连接字符串:Server、Port、Database 与 SSL 的写法
Npgsql 的连接字符串长得和 SQL Server 很像,但参数名有差别。最容易让数据库迁移过来的人写错的就是 Database,有人习惯写 Initial Catalog。在 Npgsql 里只认 Database。下面是一段我常用的最小连接串:
string connStr = "Server=192.168.10.20;Port=5432;Database=erpdb;User Id=app_user;Password=YourPass;Pooling=true;MinPoolSize=1;MaxPoolSize=50;SslMode=Disable;";这串参数里,Server 也可以写成 Host,两种写法在老版本里都认。Port 不写时默认是 5432,只要机器上装了多个 PostgreSQL 实例,或者发生过连错库的事件,我就要求必须显式写出来,不给隐患留空间。User Id 和 Password 注意大小写敏感,PostgreSQL 的用户名和密码不像 SQL Server 那样可以模糊处理。
Pooling 控制连接池开关,MinPoolSize 和 MaxPoolSize 决定池的弹性。老驱动默认开了池,但默认上限在业务量增长后往往不够。SslMode 在老版本里和 SSL 混着用,内网环境我一般直接设 SslMode=Disable,避免 TLS 协议版本不一致带来的握手问题;外网或安全要求高的环境则要按公司规范升级驱动,不能只靠这个老包硬扛。
3.2 最小可跑代码:从 Open 到 select version()
引用配好后,先用最短代码验证链路通不通。这段代码我几乎每次接老项目都会先跑一遍,确认网络、端口、用户名密码、数据库名这四个要素都没问题,再进入业务逻辑。
using System; using Npgsql; class QuickTest { static void Main(string[] args) { string connStr = "Server=127.0.0.1;Port=5432;Database=test;User Id=postgres;Password=123456;Pooling=true;"; using (NpgsqlConnection conn = new NpgsqlConnection(connStr)) { conn.Open(); using (NpgsqlCommand cmd = new NpgsqlCommand("select version();", conn)) { string version = (string)cmd.ExecuteScalar(); Console.WriteLine(version); } } } }逻辑说明:NpgsqlConnection 负责建立到 PostgreSQL 的物理连接,Open 之后连接才真正可用。select version() 返回的是数据库版本字符串,ExecuteScalar 取第一行第一列,所以可以强转成 string 后直接输出。如果这条能打出版本号,说明驱动、网络、认证三个环节都通了。这里我特意在外层 using 里创建连接,因为 using 到作用域结束会自动 Close,这是保证连接归还连接池最省心的姿势。
参数说明:连接字符串里的 Server 指向 PostgreSQL 所在主机,本地测试写 127.0.0.1,不走 Unix Socket。Port 默认 5432,显式写出更清楚。CommandTimeout 没有设置时按驱动默认值走,老版本默认一般是 20 秒,生产环境大查询要按需调大,这个后面避坑部分再说。
3.3 参数化写入:NpgsqlParameter 与 RETURNING id
连接通了就要碰业务。老项目里最常见的坏习惯是把变量直接拼进 SQL 字符串,比如 “select * from t where id=” + userId。这么做在今天任何数据库上都是高风险动作,Npgsql 提供了完整的参数化接口,用起来不复杂,只是占位符风格和 SQL Server 有差异。
int userId; using (NpgsqlConnection conn = new NpgsqlConnection(connStr)) { conn.Open(); const string sql = "INSERT INTO app_user(user_name, email) VALUES(:name, :mail) RETURNING id;"; using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn)) { cmd.Parameters.Add(new NpgsqlParameter("name", NpgsqlDbType.Text) { Value = "张伟" }); cmd.Parameters.Add(new NpgsqlParameter("mail", NpgsqlDbType.Text) { Value = "zw@example.com" }); userId = (int)cmd.ExecuteScalar(); } }逻辑说明:SQL 里的 :name 和 :mail 是参数占位符,NpgsqlParameter 对象把它们替换成安全值后再发给 PostgreSQL,值和 SQL 文本分离,注入从机制上被挡住了。RETURNING id 是 PostgreSQL 的特色语法,插入后直接把自增主键返回,省掉再查一次 sequence 的请求,这在 2.2.4.3 时代是最好用的拿主键姿势。
参数说明:NpgsqlDbType.Text 对应 PostgreSQL 的 text/varchar 类型。Value 赋的是 .NET 字符串。构造参数时第一个参数名不带冒号,Npgsql 会拿 SQL 里的占位符做匹配。这里有个养成习惯,同一句 SQL 里参数前缀要么全用冒号,要么全用 @,混着用容易触发老驱动的解析歧义,后面避坑章节单独讲。
提示:如果你是从 SQL Server 迁移过来的,最容易踩的就是占位符 @ 和冒号 : 混用,统一成一种风格再上线。
4. Npgsql 2.2.4.3 避坑指南:五个真实翻车点的现象、原因与当场处理
老驱动不是不能用,而是要知道它的边界。下面五个坑,是我把 Npgsql 2.x 接到存量项目时实际遇到最多的,按现象、原因、解决的顺序写,你可以直接对照排查。
4.1 程序集加载失败:Npgsql.dll 明明在 bin 里,Open 却报加载异常
现象:编译能通过,运行到 new NpgsqlConnection 或 conn.Open() 时抛出 System.IO.FileNotFoundException,看异常信息是 Npgsql.dll 找不见,打开 bin 目录,dll 明明就在那里。
原因:最常见的是引用的 Npgsql.dll 在项目里属性“复制本地”为 false,publish 或 build 后 bin 目录里那个 dll 是旧版本拷贝,或者压根没拷进去。另一种情况是项目同时引用了另一个组件,那个组件传递引用了更新版本的 Npgsql,运行时按新版本去绑定,自然找不到 2.2.4.3。
解决:先在项目引用里查 Npgsql 的“复制本地”属性,设为 true 后重新生成。再看解决方案里是否还有其他引用间接带入了 Npgsql,有的话要么统一版本,要么用 app.config 里的 bindingRedirect 把版本号全部指向 2.2.4.3。这一步做干净,加载异常基本消失。
4.2 SSL 握手通不过:老驱动连新 PostgreSQL 的 TLS 问题
现象:连接串里写了 SslMode=Require 或 SSL=true,程序在 Open 时报错,要么是握手超时,要么是服务器端拒绝了 TLS 版本,偶尔表现为连接一直卡住直到超时才抛异常。
原因:Npgsql 2.2.4.3 是十多年前的版本,当时 TLS 的主流协议还是 1.0/1.1。新 PostgreSQL 部署时通常已经关闭了这些老协议,强制要求 TLS 1.2 以上,两边协商不到共同版本,握手就断了。
解决:内网环境最直接的办法是连接串改成 SslMode=Disable,PostgreSQL 服务端不用 SSL,双方用明文连接。前提是网络链路本身可控。如果公司安全策略不允许明文,那这条路走不通,正确选择是升级到支持新 TLS 的 Npgsql 版本,而不是继续在这个老包上纠结。我见过硬编 TLS 底层参数和系统注册表去兼容老驱动的做法,那是拿生产环境的安全换旧代码的懒惰,不建议碰。
4.3 时间字段差 8 小时:timestamp 与 timestamptz 的时区偏移
现象:从 PostgreSQL 读出来的时间,比数据库里存的值多几个或少几个小时。更诡异的是,某些列正常,某些列偏移,对比半天也找不到规律,最后被人当成玄学。
原因:Npgsql 2.x 年代,timestamp without time zone 和 timestamp with time zone 的映射策略并不像今天这么严格。timestamptz 读取时会按服务器时区转成 .NET 的 DateTime,如果应用机器和数据库机器的时区设置不一致,或者 PostgreSQL 的 timezone 参数不是 UTC,转换后就会出现偏移。
解决:把时区的事放在数据库连接层统一掉,在连接初始化时执行 set timezone=‘UTC’,再让应用层全部按 UTC 处理,显示层再转本地。代码里对时间的比较、分组也统一用 UTC 值。如果只是展示,让前端转换;如果是业务计算,必须固定基准。这一条不光是 Npgsql 的问题,任何数据库驱动都适用。
4.4 参数前缀混用:一条 SQL 里既有 @ 又有 :,参数对不上
现象:SQL 写成 select * from t where id=@id and name=:name,代码里 Parameters 也加了两个,但运行时不是报“列不存在”就是报“运算符不存在: text = integer”,怎么检查都看不出错。
原因:Npgsql 老版本对参数前缀的解析并不是简单地把每种前缀都转成同名参数,它有自己的匹配规则,混用冒号和 @ 时,第二、第三个参数很有可能没有被正确替换,PostgreSQL 收到的 SQL 带着未解析的文本,自然报错。
解决:一条 SQL 里只用一种前缀,我自己的习惯是统一用冒号。代码里所有参数名不带前缀,NpgsqlParameter 构造时也不带前缀,让驱动自己匹配。团队写 SQL 时把这条写进规范,问题就不再复发。
4.5 连接池被耗尽:旧代码把连接开在 using 外面
现象:程序运行几个小时后,新请求打开连接时开始等待,慢慢变成超时异常,错误信息里有 connection pool 字样。重启应用后恢复,过几小时又挂在同样位置。
原因:连接池有上限,连接打开后没被归还。典型场景是代码里 new NpgsqlConnection 之后忘了 Close,return 之前 if 分支里提前跳出,或者 DataReader 读取中断但没有关闭连接。池里的连接被占满,新请求只能排队等超时。
解决:所有连接都放进 using 里,NpgsqlCommand 和 NpgsqlDataReader 同样用 using。DataReader 只要没读完,底层连接就会被占住,所以读取循环结束后要显式 reader.Close() 或直接用 using 包住。已经在生产的系统排查时,先查代码里有没有 new NpgsqlConnection 后没有配套 Close 的地方,这种问题往往几十行代码就能看出病根。
5. 一个快速的验证习惯:在 PostgreSQL 端开执行日志,反查驱动行为
接手 Npgsql 老项目时,我第一件事往往不是读代码,而是先确认数据库端到底收到了什么。驱动对开发者来说像个黑匣子,连接串参数写错、SQL 被参数化处理成什么样、实际用的数据库是不是你以为的那个,答案都在 PostgreSQL 的服务端日志里。
5.1 打开执行日志,确认驱动发出的真实 SQL
在 psql 里执行下面两行,让 PostgreSQL 把所有语句都记下来:
ALTER SYSTEM SET log_statement = 'all'; SELECT pg_reload_conf();ALTER SYSTEM 是 9.4 之后才有的写法,老版本直接改 postgresql.conf 里的 log_statement 再 reload 也一样。log_statement=all 会把每条收到的 SQL 写进日志,包括参数化语句的预处理结果。然后去看 PostgreSQL 日志目录下的文件,Linux 环境常见在数据目录的 log/ 下,或者 /var/log/postgresql/ 下,按实际安装路径找。
日志里能看到:来自 Npgsql 的连接是从哪个 IP 发起的,执行了什么 SQL,查询有没有走到预期数据库。有一个很经典的错位场景就是项目配置里 Database 写错,应用启动不报错,读到的却是另一套初始化数据,查业务代码查一天都查不出来,日志一开,连接库明明白白写在里面。
验证完记得把 log_statement 设回 none,尤其生产库,全部日志会把磁盘打满。这一步几乎零成本,却是判断问题属于驱动还是属于数据库的最快路径。这几年我把它当成接 Npgsql 老项目的第一步,而不是翻代码,几次都直接锁定了问题。希望帮到你。
本文还有配套的精品资源,点击获取