Oracle导入SQL Server遇TDS协议异常,求可行数据上传方案
将Oracle数据库中百万条记录上传至SQL Server数据仓库时,遇到以下错误,此前用类似代码通过ODBC同步SQL Server与Synergex数据库正常,但Oracle数据同步失败:
Microsoft.Data.SqlClient.SqlException: 'The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect. Table-valued parameter 1 (""), row 3, column 2: Data type 0x2A has an invalid data length or metadata length.
The data for table-valued parameter "@UserHistory" doesn't conform to the table type of the parameter. SQL Server error is: 8037, state: 83
The statement has been terminated.'
相关代码
public static async Task StreamUserHistoryToDWAsync(IConfigurationRoot config) { try { var watch = System.Diagnostics.Stopwatch.StartNew(); string query = @"WBH_DATAWAREHOUSE.WBH_UserHistory"; OracleConnection OraCn = new OracleConnection(config.GetConnectionString("SynapseProd")); await OraCn.OpenAsync(); OracleCommand cmd = new OracleCommand(query, OraCn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add(new OracleParameter("MaxBegtime", GetMaxUserHistory(config))); cmd.Parameters.Add(new OracleParameter("rcUserHistory", OracleDbType.RefCursor, ParameterDirection.Output)); OracleDataReader odr = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection); string insertQuery = "[Synapse].[InsertUserHistory]"; SqlConnection sqlCn = new SqlConnection(config.GetConnectionString("powerbi")); SqlCommand sqlCmd = new SqlCommand(insertQuery, sqlCn); sqlCmd.CommandType = CommandType.StoredProcedure; await sqlCn.OpenAsync(); SqlParameter tvp = new SqlParameter("@UserHistory", odr); tvp.SqlDbType = SqlDbType.Structured; SqlParameter rtn = new SqlParameter("@rtn_result", SqlDbType.Int); rtn.Direction = ParameterDirection.Output; sqlCmd.Parameters.Add(tvp); sqlCmd.Parameters.Add(rtn); await sqlCmd.ExecuteNonQueryAsync(); sqlCmd.Connection.Close(); watch.Stop(); var elapsed = watch.ElapsedMilliseconds; Console.WriteLine(elapsed.ToString()); logger.Info("User History elapsed time: {time}", elapsed.ToString()); } catch (Exception ex) { logger.Error(ex, "StreamUserHistoryToDW Error"); } }
SQL Server表值类型定义
CREATE TYPE [Synapse].[UserHistoryTableType] AS TABLE ( [NAMEID] [varchar](12) NOT NULL, [BEGTIME] [datetime] NOT NULL, [EVENT] [varchar](4) NOT NULL, [ENDTIME] [datetime] NULL, [FACILITY] [varchar](3) NULL, [CUSTID] [varchar](10) NULL, [EQUIPMENT] [varchar](2) NULL, [UNITS] [int] NULL, [ETC] [nvarchar](255) NULL, [ORDERID] [int] NULL, [SHIPID] [smallint] NULL, [LOCATION] [varchar](10) NULL, [LPID] [varchar](20) NULL, [ITEM] [varchar](50) NULL, [UOM] [varchar](4) NULL, [BASEUOM] [varchar](4) NULL, [BASEUNITS] [int] NULL, [CUBE] [decimal](10, 4) NULL, [WEIGHT] [decimal](17, 8) NULL, [EMPLOYEECOST] [decimal](10, 2) NULL, [EQUIPMENTCOST] [decimal](10, 2) NULL )
关键现象
- 确认
@UserHistory表值参数数据类型与Oracle返回数据完全匹配 - 即使将存储过程改为空实现(仅返回值),错误依然存在,问题出在表值参数传递环节
- 限制查询结果为2行及以下时同步正常,3行及以上触发错误,与具体数据无关
- 当前环境:
Oracle.ManagedAccess.Core23.6.1、.NET 8.0
解决方案推荐
1. 转换数据载体后传递表值参数
OracleDataReader与SQL Server表值参数的底层类型映射存在兼容性问题,不要直接传递。改为:
- 从OracleDataReader逐行读取数据,转换为符合SQL Server表值类型的
DataTable - 将
DataTable作为表值参数传入存储过程
示例代码片段:
// 构建对应表值类型的DataTable DataTable tvpData = new DataTable(); tvpData.Columns.Add("NAMEID", typeof(string)).MaxLength = 12; tvpData.Columns.Add("BEGTIME", typeof(DateTime)); // 按表值类型定义添加剩余列... // 从OracleDataReader读取数据填充DataTable while (await odr.ReadAsync()) { DataRow row = tvpData.NewRow(); row["NAMEID"] = odr.IsDBNull(0) ? DBNull.Value : odr.GetString(0); row["BEGTIME"] = odr.IsDBNull(1) ? DBNull.Value : odr.GetDateTime(1); // 处理剩余列... tvpData.Rows.Add(row); } // 使用DataTable作为表值参数 SqlParameter tvp = new SqlParameter("@UserHistory", tvpData); tvp.SqlDbType = SqlDbType.Structured;
2. 分块批量处理
对于百万级数据,每次分块处理1000-10000行,避免一次性传递过大数据集,降低内存占用和出错概率。
3. 替代同步方案
如果上述方法仍有问题,可选择更高效的批量导入方案:
- SqlBulkCopy:直接将Oracle查询结果批量写入SQL Server目标表,跳过表值参数和存储过程,性能更高、兼容性更好
- 专业ETL工具:如SSIS、Azure Data Factory,专门用于跨数据库数据同步,适合百万级以上数据量场景
内容的提问来源于stack exchange,提问作者David Gerst

