使用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
相关产品推荐
相关产品推荐

