Oracle REF CURSOR 本质是服务端结果集的句柄而非游标对象,需用OracleType.Cursor显式声明为Output参数,执行前必须注册类型、方向及严格匹配的参数名,执行后仅能通过ExecuteReader()返回的OracleDataReader一次性读取,且连接须保持打开至reader关闭。

Oracle REF CURSOR 本质是啥?
它不是真正的“游标对象”,而是 Oracle 服务端返回的一组结果集的句柄,客户端必须用 OracleCommand 配合 OracleType.Cursor 显式声明为输出参数,且该参数**必须在执行前注册**——漏掉这步,ExecuteReader() 会直接抛 System.InvalidOperationException: Invalid operation. The connection is closed. 或更隐蔽的空引用。
怎么注册 REF CURSOR 输出参数?
关键在三件事:类型设为 OracleType.Cursor、方向设为 ParameterDirection.Output、名称和存储过程定义完全一致(含大小写,Oracle 默认大写)。
常见错误包括:
- 用
DbType.Object或DbType.String代替OracleType.Cursor - 忘记调用
command.Parameters.Add(),或加了但没设Direction - 参数名写成
@rc或rc,而实际 SP 中定义的是RC_OUT
正确写法示例:
var cmd = new OracleCommand("PKG_REPORT.GET_EMP_DATA", conn);
cmd.CommandType = CommandType.StoredProcedure;
var rcParam = cmd.Parameters.Add("RC_OUT", OracleType.Cursor);
rcParam.Direction = ParameterDirection.Output;
执行后怎么读取结果?
不能用 ExecuteNonQuery(),也不能等 ExecuteReader() 返回后再去取参数值——REF CURSOR 的结果就藏在 OracleCommand.ExecuteReader() 返回的 OracleDataReader 里,而且**只能读一次**。
注意点:
- 调用
ExecuteReader()后,rcParam.Value仍是DBNull.Value,别试图从中取数据 - 如果存储过程有多个 REF CURSOR 输出参数,
ExecuteReader()返回的 reader 会按参数注册顺序自动映射为多个结果集,用reader.NextResult()切换 - 连接必须保持打开状态直到 reader 关闭,否则会报
Invalid operation. The connection is closed.
简单读取示例:
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine(reader["EMP_NAME"]);
}
}
为什么有时读不到数据或报 ORA-01002?
最常见两个原因:OracleCommand 没配 CommandType.StoredProcedure,或者连接字符串里缺 Unicode=True(尤其字段含中文时)。
另外要注意:
- REF CURSOR 必须由存储过程
OPEN ... FOR显式打开,若 SP 中逻辑跳过了 OPEN,reader 就是空的 - 某些旧版
Oracle.ManagedDataAccess(如 19.x 之前)对多结果集支持不稳,建议用 21.x+ 并确认OracleConfiguration.FetchSize不为 0 - 别在
using块外保留 reader 引用——它绑定了底层连接生命周期
真正麻烦的从来不是语法,而是那个没被显式 OPEN 的游标,或者连错数据库实例导致调用了空壳 SP。


















