如何使用Npgsql从PostgreSQL存储过程的refcursor参数获取数据
解决PostgreSQL存储过程INOUT refcursor参数调用报错问题
问题原因
你遇到的34000: cursor "p_refcur" does not exist错误,核心原因有两个:
- PostgreSQL的游标是事务绑定的,默认执行
ExecuteNonQuery()后,隐式事务会自动提交,游标随即被销毁。 - 硬编码使用传入的游标名
p_refcur无效,存储过程返回的INOUT参数实际返回的是PostgreSQL内部生成的游标标识,并非你传入的字面名称。
解决方案
无需修改存储过程,只需调整C#调用代码,保留事务上下文并读取存储过程返回的实际游标值:
修正后的代码示例
using (var dataSource = NpgsqlDataSource.Create(this.connectionString)) { // 显式开启连接与事务,维持游标生命周期 using (var conn = dataSource.OpenConnection()) using (var tx = conn.BeginTransaction()) { using (var command = conn.CreateCommand()) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "myschema_dbo.usp_getcustomers"; command.Transaction = tx; // 添加输入参数 command.Parameters.Add(new NpgsqlParameter() { ParameterName = "par_includeinactive", NpgsqlDbType = NpgsqlDbType.Numeric, Direction = ParameterDirection.Input, Value = 1 }); // 定义游标参数,无需预赋值 var refCurParam = new NpgsqlParameter() { ParameterName = "p_refcur", NpgsqlDbType = NpgsqlDbType.Refcursor, Direction = ParameterDirection.InputOutput }; command.Parameters.Add(refCurParam); // 执行存储过程 command.ExecuteNonQuery(); // 从输出参数获取实际游标名称 var cursorName = refCurParam.Value.ToString(); // 构造FETCH命令并清空原有参数 command.CommandText = $"fetch all in \"{cursorName}\""; command.CommandType = CommandType.Text; command.Parameters.Clear(); // 读取数据 using (var reader = command.ExecuteReader()) { while (reader.Read()) { // 处理数据逻辑示例 var customerName = reader.GetString(reader.GetOrdinal("customername")); var isActive = reader.GetBoolean(reader.GetOrdinal("isactive")); var email = reader.GetString(reader.GetOrdinal("emailaddress")); } } // 提交事务 tx.Commit(); } } }
关键修改点
- 显式开启事务:确保游标在数据读取全程保持有效,避免隐式提交导致游标销毁。
- 不预赋值游标参数:让PostgreSQL自动生成正确的游标标识,执行后从输出参数读取实际名称。
- 执行FETCH前清空参数:避免原有参数残留引发SQL语法错误。
额外说明
这套逻辑可批量适配所有自动生成的、带INOUT refcursor参数的存储过程,无需逐个修改数据库端代码。
内容的提问来源于stack exchange,提问作者Jonathan Lawson
相关产品推荐
相关产品推荐

