OUT游标必须用OracleDbType.RefCursor声明,C#需用OracleDynamicParameters并设ParameterDirection.Output,且必须调用QueryAsync()而非ExecuteAsync()才能正确映射结果集。

Oracle存储过程中OUT游标必须用OracleDbType.RefCursor声明
Oracle的REF CURSOR类型在Dapper中不会自动映射,如果在存储过程中把OUT参数简单声明为sys_refcursor但C#端没配对使用OracleDbType.RefCursor,Dapper会抛出Invalid operation. The connection is closed.或Unable to cast object of type 'Oracle.ManagedDataAccess.Types.OracleRefCursor' to type 'System.Data.IDataReader'。关键不是“能不能用”,而是“怎么告诉Oracle驱动:这个OUT参数我要当结果集读”。
实操建议:
- 存储过程定义里OUT参数必须显式指定
OUT SYS_REFCURSOR(不能只写OUT) - C#中注册该参数时,
DbType设为DbType.Object,但OracleDbType必须设为OracleDbType.RefCursor - 用
OracleDynamicParameters而非原生DynamicParameters,否则OracleDbType属性不可用
必须用QueryAsync() 而非ExecuteAsync()来消费REF CURSOR
ExecuteAsync()只返回影响行数,它根本不会尝试解析REF CURSOR;只有QueryAsync(或Query)才会触发Dapper对OracleRefCursor的内部转换逻辑——把它转成IDataReader再映射到目标类型。如果你调了ExecuteAsync()后发现数据“丢了”,大概率是这一步错了。
常见错误现象:
- 调用成功、无异常、但返回空集合(
new List)() - 存储过程里
OPEN out_cursor FOR SELECT ...已执行,但C#侧收不到任何行
正确做法:
- 确保传入
QueryAsync的SQL字符串是() "BEGIN your_pkg.your_proc(:p_in, :p_out); END;"形式(即PL/SQL块) -
QueryAsync的泛型参数() T必须与游标SELECT的列结构兼容(字段名/类型能匹配) - 不要试图在同一个
QueryAsync调用里同时读多个OUT游标(Dapper不支持多结果集)
OracleDynamicParameters要显式Add并SetDirection
Dapper默认不识别Oracle专有类型,必须用Oracle官方提供的OracleDynamicParameters(来自Oracle.ManagedDataAccess.Core包),且每个REF CURSOR参数必须手动Add()并设置Direction = ParameterDirection.Output。漏掉SetDirection会导致参数被当作输入值传入,存储过程可能报ORA-06550: wrong number or types of arguments。
示例关键片段:
var p = new OracleDynamicParameters();
p.Add("p_in", "value", DbType.String, ParameterDirection.Input);
p.Add("p_out", dbType: OracleDbType.RefCursor, direction: ParameterDirection.Output); // 必须写direction
var results = await conn.QueryAsync<mydto>("BEGIN my_pkg.get_data(:p_in, :p_out); END;", p);
</mydto>
注意连接状态和事务上下文
Oracle REF CURSOR绑定的是当前连接的会话状态,如果连接被池回收、或在事务中执行后未提交就释放连接,游标可能提前关闭。尤其在ASP.NET Core中,若在IEnumerable上延迟执行(比如没立刻.ToList()),等真正枚举时连接早已归还给池,就会报Connection is closed。
规避方式:
- 立即执行映射:
await conn.QueryAsync,别留着(...).ToListAsync() IEnumerable到处传 - 避免在
using块外消费结果 - 确认连接字符串里
Pooling=true(默认开启),但不要依赖“连接没关”来维持游标——REF CURSOR生命周期由Oracle服务端控制,客户端只能及时读完
最易被忽略的一点:Oracle存储过程里的游标OPEN语句必须在RETURN或END前执行完毕,Dapper不会帮你重试或等待。如果过程里有异常分支没OPEN游标,C#侧拿到的就是null,QueryAsync会直接返回空集合,连异常都不会抛。











