必须用 OracleException 捕获 RAISE_APPLICATION_ERROR 异常,因 ODP.NET 将其映射为 OracleException 而非 Exception 子类;catch(Exception) 无法获取 ex.Number,导致无法区分业务错误(如 20001)与底层错误(如 12152),且 ex.Message 不可靠,应直接读取 ex.Number 并注意其为正整数。
能捕获,但必须用 oracleexception 类型判断,不能靠 message 字符串解析或泛化 exception 捕获。
为什么 catch(Exception) 会漏掉业务异常?
.NET 的 Oracle Data Provider(ODP.NET)把 RAISE_APPLICATION_ERROR 映射为 OracleException,不是普通 Exception 子类。如果只写 catch(Exception ex),虽然语法通过,但实际无法区分它是连接超时、SQL 语法错,还是你定义的 -20001 业务错误。
- 错误写法:
catch(Exception ex)→ 丢失Number属性,无法精准识别 ORA 编号 - 正确做法:显式
catch(OracleException ex),再读ex.Number - 注意:
ex.Number返回的是正整数(如RAISE_APPLICATION_ERROR(-20001, ...)→ex.Number == 20001),不是负数,也不是字符串 "ORA-20001"
如何从 OracleException 提取自定义错误码?
别碰 ex.Message —— 它可能被本地化(比如中文环境返回“余额不足”,英文环境变“Insufficient balance”),也可能含换行或额外堆栈,不可用于逻辑分支。
- 直接读
ex.Number:它就是你传给RAISE_APPLICATION_ERROR的负数参数的绝对值 - 若需日志记录完整错误标识,拼成
"ORA-" + ex.Number即可(例如20001→"ORA-20001") - 如果
ex.Number是 0、-1 或 12152 这类非 -20xxx 范围的值,说明不是你主动抛的业务异常,而是网络中断、TNS 错误等底层问题,应走另一套恢复逻辑
异常在哪一行抛出?DML 后立刻 catch,不是 Commit 时
触发器里的 RAISE_APPLICATION_ERROR 会在引发它的 DML 语句执行完毕后、事务提交前立即抛出。也就是说,异常发生在 ExecuteNonQuery() 或 ExecuteScalar() 调用内部,而不是 transaction.Commit() 那一刻。
- 危险写法:
cmd.ExecuteNonQuery(); trans.Commit();然后只在Commit()外包 try-catch → 触发器报错根本进不到这里,直接崩在第一行 - 安全写法:DML 和 catch 紧挨着,例如:
try { cmd.ExecuteNonQuery(); } catch (OracleException ex) when (ex.Number == 20001) { // 处理余额不足 } - 不要依赖
ex.Source或ex.StackTrace判断来源——它们指向 ODP.NET 内部,和你的 PL/SQL 无关
容易忽略的兼容性细节
不同版本 ODP.NET 对 OracleException.Number 的填充一致性较好,但老版本(如 11.2 client 配合 .NET Framework 4.0)偶尔会把某些自定义错误映射成 ex.Number == 0。如果遇到这种情况,先确认是否启用了 Enable Extended SQL Error Information(连接字符串加 Extended Sql Error Information=true),否则驱动可能截断错误上下文。
另外,RAISE_APPLICATION_ERROR 的错误消息长度上限是 2048 字节,超长会被静默截断——这不是 .NET 层的问题,是 Oracle 服务端行为,客户端看到的 ex.Message 可能不完整,所以永远别把它当结构化数据用。


















