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

使用SqlBulkCopy导入CSV至SQL表列映射不匹配问题解决

问题根因

SqlBulkCopy 默认列名匹配规则为大小写敏感,你当前代码直接用CSV解析出的原始列名做映射,只要CSV列名和SQL目标表列名存在大小写差异,就会触发The given ColumnMapping does not match up with any column in the source or destination报错。
除此之外现有代码还有两个隐患:

  • 手动拆分连接字符串的逻辑没有兼容键名大小写差异,一旦连接串里的键是data source/initial catalog这种小写格式,会直接抛出键不存在的错误
  • 没有做列存在性校验,CSV出现多余列、或者目标表列在CSV中缺失时,无法提前给出明确错误提示,只会抛出泛化的映射异常
修复方案

核心思路是把列匹配逻辑改成大小写不敏感,同时提前从数据库拉取目标表真实列名做映射,替换掉原来直接硬匹配原始字符串的逻辑,另外用官方连接字符串构造器替换手动拆分逻辑,避免兼容性问题。
修改后的完整可运行代码如下:

public void Main()
{
    // 实际使用时可将mSQLTable改为从DTS变量读取,和mFilepath保持一致
    var mFilepath = Dts.Variables["InputFile"].Value.ToString();
    var mSQLTable = "[Staging].[tblLoadBUF]";

    try
    {
        DataTable dt = new DataTable();
        string contents = File.ReadAllText(mFilepath, System.Text.Encoding.GetEncoding(1252));
        TextFieldParser parser = new TextFieldParser(new StringReader(contents));
        parser.HasFieldsEnclosedInQuotes = true;
        parser.SetDelimiters(",");
        string[] fields;

        while (!parser.EndOfData)
        {
            fields = parser.ReadFields();
            // 统一清洗字段值:去除包裹引号、空值转DBNull
            var cleanedFields = fields.Select(item => 
                string.IsNullOrWhiteSpace(item.Trim('"')) ? null : item.Trim('"')
            ).ToArray();

            if (dt.Columns.Count == 0)
            {
                foreach (string field in cleanedFields)
                {
                    // DataTable保留CSV原始列名,避免后续行数据赋值出错
                    dt.Columns.Add(new DataColumn(field, typeof(string)));
                }
            }
            else
            {
                dt.Rows.Add(cleanedFields);
            }
        }
        parser.Close();

        // 从OLEDB连接串提取属性构造SqlClient专属连接串,兼容键名大小写
        string rawConnStr = Dts.Connections["OLEDB_CONN"].ConnectionString;
        var oleDbProps = rawConnStr.Trim(';').Split(';')
            .Select(x => x.Split('='))
            .ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase);
        
        SqlConnectionStringBuilder connBuilder = new SqlConnectionStringBuilder
        {
            DataSource = oleDbProps["Data Source"],
            InitialCatalog = oleDbProps["Initial Catalog"],
            IntegratedSecurity = bool.Parse(oleDbProps["Integrated Security"])
        };

        using (SqlConnection con = new SqlConnection(connBuilder.ConnectionString))
        {
            con.Open();
            // 拉取目标表的真实列名,用不区分大小写的字典存储
            var targetColumns = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            string getColumnSql = $@"
                SELECT COLUMN_NAME 
                FROM INFORMATION_SCHEMA.COLUMNS 
                WHERE TABLE_NAME = PARSENAME('{mSQLTable}',1)
                AND TABLE_SCHEMA = PARSENAME('{mSQLTable}',2)
                -- 如果目标表有自增列、默认值列不需要CSV传入,可在这里加条件过滤,比如 AND COLUMN_NAME NOT IN ('Id','CreateTime')
            ";
            using (SqlCommand cmd = new SqlCommand(getColumnSql, con))
            using (SqlDataReader reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    string colName = reader.GetString(0);
                    targetColumns[colName] = colName;
                }
            }

            using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(con))
            {
                sqlBulkCopy.DestinationTableName = mSQLTable;
                sqlBulkCopy.ColumnMappings.Clear();
                List<string> unmatchedCsvColumns = new List<string>();

                foreach (DataColumn csvCol in dt.Columns)
                {
                    // 大小写不敏感匹配目标列
                    if (targetColumns.TryGetValue(csvCol.ColumnName, out string actualTargetColName))
                    {
                        // 目标列用数据库返回的真实大小写名称,源列用DataTable的原始列名,保证SqlBulkCopy能正确识别
                        sqlBulkCopy.ColumnMappings.Add(csvCol.ColumnName, actualTargetColName);
                    }
                    else
                    {
                        unmatchedCsvColumns.Add(csvCol.ColumnName);
                    }
                }

                // 存在未匹配列时直接抛出明确错误,避免数据错位
                if (unmatchedCsvColumns.Any())
                {
                    throw new Exception($"CSV中存在目标表未定义的列:{string.Join(", ", unmatchedCsvColumns)}");
                }

                sqlBulkCopy.WriteToServer(dt);
            }
            con.Close();
        }                    

        Dts.TaskResult = (int)ScriptResults.Success;
    }
    catch (Exception ex)
    {
        Dts.Events.FireError(0, "CSV加载任务失败", ex.ToString(), string.Empty, 0);
        Dts.TaskResult = (int)ScriptResults.Failure;
    }
}
可选调整项
  • 如果允许CSV存在目标表不需要的多余列,直接删除unmatchedCsvColumns相关的抛错逻辑即可,多余列会被自动忽略,不影响已匹配列的导入
  • 如果需要校验目标表必填列是否在CSV中存在,可以在生成完映射后,对比targetColumns的键和已映射的列,把缺失的必填列抛出提示
  • 解析CSV表头时可以额外加一步Trim()处理前后空格,避免因为表头带不可见空白字符导致匹配失败

内容的提问来源于stack exchange,提问作者MChalut

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:51:22