REF CURSOR参数必须显式声明为OracleDbType.RefCursor且Direction=Output,命名需与PL/SQL一致;消费时须用GetDataReader()或Fill(),参数顺序须严格匹配存储过程声明,连接字符串需设Unicode=True。

REF CURSOR参数必须显式声明为OracleDbType.RefCursor
ODP.NET不会自动推断REF CURSOR类型,即使存储过程签名明确,也必须手动设置OracleParameter的OracleDbType为RefCursor,否则会报ORA-06550: PLS-00306(参数类型不匹配)错误。
常见错误是直接用DbType.Object或留空OracleDbType,这会导致绑定失败。正确做法:
var refCursorParam = new OracleParameter("p_result", OracleDbType.RefCursor);
refCursorParam.Direction = ParameterDirection.Output;
cmd.Parameters.Add(refCursorParam);
- 必须设
Direction = ParameterDirection.Output(不能是InputOutput) - 参数名要和PL/SQL中定义的游标变量名完全一致(区分大小写,取决于定义方式)
- 如果使用命名绑定(
:p_result),确保命令文本中实际使用了该占位符
执行后必须用OracleDataAdapter.Fill()或OracleDataReader.Read()消费结果集
REF CURSOR返回的是一个未绑定的游标句柄,ODP.NET不会自动将其转为DataTable或DataSet;直接访问cmd.ExecuteNonQuery()只会返回受影响行数(通常是-1),而游标内容仍处于挂起状态。
两种主流消费方式:
- 用
OracleDataAdapter填充DataTable:var adapter = new OracleDataAdapter(cmd); adapter.Fill(dataTable); // 此时才真正拉取数据
- 用
OracleDataReader逐行读取:cmd.ExecuteNonQuery(); // 先执行 using (var reader = ((OracleRefCursor)refCursorParam.Value).GetDataReader()) { while (reader.Read()) { /* 处理每行 */ } }注意:必须通过((OracleRefCursor)refCursorParam.Value).GetDataReader()获取reader,不能对cmd.ExecuteReader()调用——那会尝试重新执行命令
多个REF CURSOR输出时,参数添加顺序必须与存储过程声明顺序严格一致
ODP.NET按参数在Parameters集合中的索引位置匹配PL/SQL形参,而不是按名称。哪怕你用了命名绑定,只要顺序错,就会把第一个游标值塞进第二个参数,导致Invalid operation on a closed cursor或字段映射错乱。
- 例如存储过程定义为
PROCEDURE get_data(p_in NUMBER, p_out1 OUT SYS_REFCURSOR, p_out2 OUT SYS_REFCURSOR),则C#中必须先加p_out1再加p_out2 - 建议用
cmd.Parameters.AddRange(new[] { param1, param2 })显式控制顺序,避免Add()调用分散在多处带来的隐患 - 若使用
OracleCommand.BindByName = true,仅对输入参数生效,对REF CURSOR无效
连接字符串需启用Unicode并注意字符集兼容性
当REF CURSOR返回含中文、特殊符号的列时,若连接字符串未指定Unicode=true,可能出现乱码或ORA-01401: inserted value too large for column(因字符宽度计算错误)。
- 连接字符串中必须包含
Unicode=True(ODP.NET Core 5+ 默认开启,但旧版需显式设置) - 避免混用
charset=AL32UTF8和Unicode=False,后者会强制使用数据库字符集转换,易出错 - 如果查询涉及XMLType或CLOB字段,REF CURSOR中对应列需在PL/SQL里显式
CAST或TO_CLOB,否则ODP.NET可能无法识别其类型
REF CURSOR真正的复杂点不在声明,而在生命周期管理:它依赖底层Oracle会话保持打开状态,一旦连接关闭或命令释放,游标句柄就失效。所以别在using块里过早释放OracleCommand,而要把OracleRefCursor.GetDataReader()或Fill()放在同一作用域内完成。


















