必须用odp.net unmanaged driver(oracle.dataaccess.dll),因其独有oracleaqmessage及enqueue/dequeue方法,managed driver已移除aq相关api,调用会直接报typeloadexception;且需匹配位数oracle client、.net framework≥4.5、正确设置messagetype/udttypename、启用队列并授权dequeue权限。

必须用 ODP.NET Unmanaged Driver(Oracle.DataAccess.dll),Managed Driver(Oracle.ManagedDataAccess.dll)不支持 OracleAQMessage 和 Enqueue/Dequeue 方法。
为什么 Oracle.ManagedDataAccess 不行
OracleAQMessage 类仅存在于 Oracle.DataAccess.dll 中,且其 Enqueue() 和 Dequeue() 方法依赖底层 OCI 调用。Managed Driver 剥离了 AQ 相关 API,调用 OracleAQQueue.Enqueue() 会直接报 TypeLoadException 或“找不到类型”。即使 NuGet 安装了最新版 Oracle.ManagedDataAccess,也无法实例化 OracleAQMessage。
- 检查引用:项目必须引用
Oracle.DataAccess.dll(通常位于 Oracle Client 安装目录,如ORACLE_HOME\odp.net\bin\4\Oracle.DataAccess.dll) - .NET Framework 版本需 ≥ 4.5(
Oracle.DataAccess不支持 .NET Core/.NET 5+) - 运行时需安装对应位数的 Oracle Client(x64 程序配 x64 Client,否则报
BadImageFormatException)
Enqueue() 前必须设置 MessageType 和 UdtTypeName
如果 payload 是自定义 UDT(比如 t_spl_queue_payload),OracleAQMessage.MessageType 必须设为 OracleAQMessageType.UDT,且 UdtTypeName 必须精确匹配数据库中创建的 type 全名(含 schema,如 "USER1"."T_SPL_QUEUE_PAYLOAD")。漏设或大小写不一致会导致 ArgumentException:“The message payload is OracleXmlType and the connection used to create OracleXmlType is different from the queue's connection”——这个错误信息有误导性,实际常因 UdtTypeName 为空或错配触发。
- 不要用
new OracleAQMessage { Payload = obj }后直接 enqueue;先 new,再赋值MessageType和UdtTypeName,最后设Payload - UDT 实例必须由同一
OracleConnection创建(即 payload 对象的构造上下文要和OracleAQQueue的 connection 一致) - 若用 RAW 或 JSON payload,
MessageType应设为OracleAQMessageType.RAW或OracleAQMessageType.JSON,此时UdtTypeName必须为null
Dequeue() 卡住或抛 ORA-25228 的真实原因
Dequeue() 默认阻塞等待,超时后抛 OracleException 错误信息为 ORA-25228: timeout or end-of-fetch during message dequeue。这不是网络问题,而是队列未启用或消费者权限不足。
- 确认队列已启动:
EXEC DBMS_AQADM.START_QUEUE('YOUR_QUEUE_NAME');—— 没启动就 dequeue,永远等不到消息 - 检查授权:用户必须有
DEQUEUE权限,不是靠DEQUEUE_ANY就行;若队列是私有队列,需显式执行DBMS_AQADM.GRANT_QUEUE_PRIVILEGE('DEQUEUE', 'YOUR_QUEUE_NAME', 'YOUR_USER', FALSE) -
DequeueOptions.Wait设为-1表示无限等待;设为 0 表示立即返回(无消息则返回 null);设为正数单位是秒,超时即抛异常 - 若用多消费者队列,
DequeueOptions.ConsumerName必须与DBMS_AQADM.ADD_SUBSCRIBER中注册的 name 严格一致(区分大小写)
消息 ID 为空?那是缓冲模式在作怪
调用 Enqueue() 后读 message.MessageId 是 null,常见于两种情况:一是用了缓冲队列(buffered queue),二是没 commit。Oracle AQ 默认是非事务性入队,但 MessageId 只在 commit 后才生成并可读取。缓冲队列(DBMS_AQADM.CREATE_QUEUE_TABLE(..., queue_payload_type => '...') 未指定 storage_clause 且未禁用缓冲)本身就不生成 MessageId。
- 生产环境应避免缓冲队列,改用普通队列表(
multiple_consumers => TRUE即可支持并发消费) - 确保
Enqueue()后调用connection.Commit();若用OracleTransaction,需显式传入 transaction 到OracleAQQueue构造函数 - 不要在 enqueue 后立刻读
MessageId并存日志——它此时就是 null;应在 commit 成功后再查user_queue_messages视图获取真实 ID
真正容易被忽略的是连接生命周期管理:每个 OracleAQQueue 实例绑定一个 OracleConnection,该 connection 必须保持 open 状态直到 enqueue/dequeue 完成并 commit。connection 关闭后调用 Enqueue() 会直接抛 InvalidOperationException —— 这个异常比 SQL 错误更早出现,也最容易被当成配置问题排查半天。











