OracleDataReader读取TIMESTAMP时DateTime不可表示异常修复求助
解决Oracle TIMESTAMP转.NET DateTime时的ArgumentOutOfRangeException异常
问题根源
异常出现在OracleDataReader.GetDateTime方法调用阶段,这说明在ChoETL的BeforeRecordFieldWrite事件触发之前,Oracle驱动就已经尝试把数据库中的TIMESTAMP值转换成.NET DateTime失败了。你之前的修复代码根本没机会执行,因为异常发生在更早的数据读取环节。
Oracle允许存储一些.NET DateTime不支持的日期值(比如公元前的日期、无效日历日期如2月30日),而Oracle驱动在自动转换这些值时会直接抛出ArgumentOutOfRangeException。
无需修改查询语句的解决方案
核心思路是绕过OracleDataReader自动转换DateTime的逻辑,手动读取Oracle原生的OracleTimestamp类型,再安全处理转换:
using (OracleCommand command = new OracleCommand(query, connection)) { using (OracleDataReader reader = command.ExecuteReader()) { // 预存所有列的Oracle数据类型与列名,避免逐行重复查询 var columnOracleTypes = new List<OracleDbType>(); var columnNames = new List<string>(); for (int i = 0; i < reader.FieldCount; i++) { columnOracleTypes.Add(reader.GetOracleDbType(i)); columnNames.Add(reader.GetName(i)); } using (var parser = new ChoParquetWriter(parquetSourcePath) .Configure(c => c.RowGroupSize = 1000) .NotifyAfter(1000) .OnRowsWritten((o, e) => Console.WriteLine($"Rows: {e.RowsWritten} <--- {DateTime.Now}"))) { if (reader.HasRows) { while (reader.Read()) { var processedRow = new Dictionary<string, object>(); for (int i = 0; i < reader.FieldCount; i++) { string colName = columnNames[i]; if (reader.IsDBNull(i)) { processedRow[colName] = null; continue; } OracleDbType colType = columnOracleTypes[i]; // 处理所有TIMESTAMP相关类型 if (colType is OracleDbType.TimeStamp or OracleDbType.TimeStampLTZ or OracleDbType.TimeStampTZ) { try { // 读取Oracle原生Timestamp对象,再尝试转换为.NET DateTime OracleTimestamp oracleTs = reader.GetOracleTimestamp(i); processedRow[colName] = oracleTs.Value; } catch (ArgumentOutOfRangeException) { // 转换失败时设置为null或自定义默认值 processedRow[colName] = null; // 可选:记录错误行信息便于排查 // Console.WriteLine($"Invalid date in row {reader.GetValue(reader.GetOrdinal("ROWID"))}, column {colName}"); } } else { // 其他类型直接读取原始值 processedRow[colName] = reader.GetValue(i); } } // 将处理后的行写入Parquet parser.Write(processedRow); } } } } }
方案细节说明
- 预读取列元数据:提前识别所有TIMESTAMP类列,避免每一行重复查询列类型,提升性能
- 手动处理OracleTimestamp:
GetOracleTimestamp方法不会直接抛出转换异常,仅当调用其Value属性转换为.NET DateTime时才可能触发异常,给了我们捕获处理的机会 - 异常隔离处理:针对无效日期单独捕获异常,设置默认值后不影响后续行的处理
- 逐行控制转换逻辑:不再直接将DataReader传给ChoParquetWriter,而是完全控制每一行数据的读取与转换过程
为什么之前的修复无效?
BeforeRecordFieldWrite事件是ChoETL在已经从DataReader读取到值之后触发的,但你的异常发生在DataReader读取值的阶段(OracleDataReader.GetDateTime),此时事件还未触发,所以你的修复代码根本不会执行。
内容的提问来源于stack exchange,提问作者user24470430
相关产品推荐
相关产品推荐

