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

Oracle导入SQL Server遇TDS协议异常,求可行数据上传方案

问题:Oracle百万条数据同步至SQL Server数据仓库时表值参数报错

将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.Core 23.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:00:58