Oracle REF CURSOR 是 Oracle 服务端的结果集指针,非实际数据;.NET 中必须显式声明为 OracleDbType.RefCursor、Direction=Output,再通过 OracleDataAdapter.Fill() 或 ExecuteReader() 获取数据,不可直接当 DataTable 使用。
REF CURSOR 是 Oracle 的游标类型,本质是服务端打开的一个结果集指针,不是数据本身。C# 的 OracleCommand 默认不自动获取它指向的数据,你调用 ExecuteNonQuery() 或漏设参数方向,只会得到 null 或报错 ORA-06550,根本拿不到行。
关键点在于:REF CURSOR 必须声明为 OracleDbType.RefCursor 类型,并且方向必须是 ParameterDirection.Output;否则驱动不知道该把它当数据源还是占位符。
ExecuteScalar() —— 它只取第一列第一行,REF CURSOR 不适用command.BindByName = true(尤其有多个参数时),否则位置绑定容易错乱OPEN :cursor_name FOR SELECT ... 显式打开,不能只声明变量这是最稳妥、兼容性最好的方式,尤其适合需要多结果集或后续要绑定到 WinForms/WPF 控件的场景。核心是让 OracleDataAdapter 自动处理 REF CURSOR 的提取和转换。
var conn = new OracleConnection(connectionString);var cmd = new OracleCommand("PKG_MY.GET_EMPLOYEES", conn);cmd.CommandType = CommandType.StoredProcedure;var cursorParam = cmd.Parameters.Add("p_cursor", OracleDbType.RefCursor); cursorParam.Direction = ParameterDirection.Output;
var adapter = new OracleDataAdapter(cmd); var ds = new DataSet(); adapter.Fill(ds); // 这一步才真正执行并读取游标内容
// 结果在 ds.Tables[0] 里
"PKG_MY.GET_EMPLOYEES"),不然 Oracle 找不到Fill() 会按声明顺序填入 ds.Tables[0]、ds.Tables[1]…Unicode=true,否则中文字段可能乱码如果你只需要遍历一次、不做缓存、也不需要 Schema 信息,OracleDataReader 内存占用更低,响应更快。但要注意:游标必须在连接打开状态下读取,且不能跳过行或倒退。
conn.Open();using (var reader = cmd.ExecuteReader()){while (reader.Read()){string name = reader.GetString(0);int id = reader.GetInt32(1);// 处理每一行}}// reader.Close() 或 using 块结束时,底层游标才关闭
reader.Read() 外访问字段(比如先 reader.FieldCount 再循环),某些驱动版本会抛 InvalidOperationException
OPEN 就返回,ExecuteReader() 会直接报 ORA-1001: invalid cursor
reader 活跃时调用 conn.Close(),否则读取中途断连出错时别急着改 C# 代码,先看 Oracle 端是否已就绪。下面三个错误几乎都指向服务端配置或参数匹配问题:
ORA-06550: line X, column Y: PLS-00306: wrong number or types of arguments → 存储过程中参数名/数量/CURSOR 变量名和 C# 里 Parameters.Add() 不一致ORA-01001: invalid cursor → 存储过程里忘了 OPEN p_cursor FOR ...,或用了 RETURN 而非 OUT 参数传游标ORA-06504: PL/SQL: Return types of call statement and actual do not match → C# 中参数类型写成 OracleDbType.Cursor(旧版写法)而非 OracleDbType.RefCursor
REF CURSOR 的“复杂”不在 C# 侧,而在它要求前后端严格约定:谁打开、谁关闭、类型是否匹配、连接生命周期是否可控。一旦某环松动,错误就出现在最不直观的地方。