<p>Oracle NUMBER(38) 转 C# decimal 存在精度截断风险,因 decimal 仅支持28位有效数字;应优先用 OracleDataReader.GetValue() 获取 OracleDecimal 再取 .Value,或用 GetString() + decimal.TryParse() 确保38位精度不丢失。</p>

Oracle NUMBER 到 C# Decimal 的精度截断风险
Oracle NUMBER 最高支持 38 位精度,而 .NET decimal 仅支持 28 位有效数字。当数据库列定义为 NUMBER(38,10) 且实际值超过 28 位有效数字(例如 1234567890123456789012345678.1234567890),直接用 OracleDataReader.GetDecimal() 会抛出 OverflowException 或静默截断——这不是驱动 bug,而是类型能力边界问题。
绕过 GetDecimal():用 OracleDecimal.Value 或字符串中转
必须避开自动转换路径,改用底层可控方式:
- 对可能超限的列,优先调用
OracleDataReader.GetValue(),它返回OracleDecimal实例;再访问其.Value属性(注意:不是.ToString()后再 parse) - 若
.Value仍触发溢出(如某些旧版 ODP.NET),退一步用GetString()获取原始字符串表示,再传给decimal.TryParse(text, out decimal result)—— 这能保全全部有效数字,前提是字符串本身没被驱动提前截断 - 避免用
Convert.ToDecimal()或(decimal)val强转OracleDecimal,它会走不安全的隐式转换链
连接字符串与驱动版本的关键配置
不同 ODP.NET 版本行为差异极大:
- Oracle.ManagedDataAccess 21.x+ 默认启用更严格的精度校验,
GetDecimal()更早失败,但GetValue()+.Value更可靠 - 19.x 可能容忍部分溢出,但结果不可控;建议统一升级到 21.12 或更高
- 连接字符串中加
Validate Connection=true;Connection Timeout=30;可减少因连接复用导致的类型缓存污染 - 不要设
Unicode=True来“解决精度问题”——它只影响字符,和 NUMBER 无关,反而可能干扰 NLS 设置
Dapper / EF Core 场景下的隐式陷阱
ORM 会掩盖底层类型转换逻辑,错误常在映射层发生:
- Dapper 默认把
NUMBER映射为decimal,一旦超限就直接炸;需改用Query<dynamic>()或自定义TypeHandler<decimal>,内部走OracleDecimal.Value - EF Core 执行原始 SQL 时,若用
FromSqlRaw().AsEnumerable(),仍走默认映射;必须配合AsNoTrackingWithIdentityResolution()并手动处理 reader - 实体类字段声明为
decimal?不代表能接住超限值——驱动在填充对象前就已尝试转换并失败
关键点在于:精度丢失不是“能不能读出来”的问题,而是“以什么形式读出来才可控”。只要跳过所有自动类型推断路径,坚持用 OracleDecimal 原生值或原始字符串,就能守住那 38 位底线。多数人栽在以为 GetDecimal() 是唯一正途。


















