You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 09:00:05