最近在做一个工具改造,实现A库数据导B库的功能。今天遇到一个奇怪问题,“从 bcp 客户端收到一个对 colid 7 无效的列长度”。
代码是从A库执行SELECT语句到本地DataTable,再将DataTable插入到B库,插入的过程是通过SqlBulkCopy实现的,这样做的好处是不用写INSERT语句,只要DataTable的列名和B库表中列名一致,则传入DataTable直接插入即可。
这里贴一下工具方法,有需要的可以参考一下:
public class DataHandler {/// <summary>/// 把DataTable中数据快速插入指定表中/// </summary>/// <param name="connectionString">目标连接字符</param>/// <param name="tableName">目标表(数据库表名)</param>/// <param name="dataSource">源数据</param>public void SqlBulkCopyByDatatable(string connectionString, string tableName, DataTable dataSource, ref string msg){SqlTransaction tran = null;//声明一个事务对象using (SqlConnection conn = new SqlConnection(connectionString)){conn.Open();using (tran = conn.BeginTransaction()){using (SqlBulkCopy sqlbulkcopy = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, tran)){try{sqlbulkcopy.DestinationTableName = tableName;sqlbulkcopy.BatchSize = 100;// 这里的Dt处理一下,删掉RID,避免列不一致导致插入失败if (dataSource.Columns.Contains("RID")){dataSource.Columns.Remove("RID");}// 设置列映射for (int i = 0; i < dataSource.Columns.Count; i++){sqlbulkcopy.ColumnMappings.Add(dataSource.Columns[i].ColumnName, dataSource.Columns[i].ColumnName);}sqlbulkcopy.WriteToServer(dataSource);tran.Commit();}catch (Exception ex) {msg = ex.Message;tran.Rollback();}}}}} }
dt有个RID,是因为外层我的查询字符串处理了根据RID处理了分页,所以这里插入前处理下这个列,下面的设置列映射很好用,如果不映射的话默认是一列一列对齐插入的,遇到表字段非常多的情况,写起来很麻烦。
言归正传,“从 bcp 客户端收到一个对 colid 7 无效的列长度”,这个错误是怎么来的呢?看现象是DataTable和表之间数据长度不一致,我的DataTable如下:
colid 7,我原本一位是d_branch_no字段,因为从0开始数的话,索引为7的刚好是d_branch_no,所以分析是这个列没有值导致报错。后面发现不是。
colid 7是指DataTable的第7列,从1开始数!初看branch_no是没有问题的,我们数据库这个字段在我现在要用的表里就是6位,那为什么报错呢?
眼睛看了好久看不出来,把查DataTable的语句在查询分析器中通过INSERT INTO ... SELECT ... FROM 的形式执行是正常的,这可就难到我了。这种问题一般就是某个细节没注意到,和预期不一致导致问题产生。
果然,我想起我们系统里的“历史债务”,很多表的字段用的char类型,char类型当位数不足时自动用空格补齐内容。
比如field字段设计的是char(6),如果field存进去的是00,那么这个字段在数据库的值是 “00 ”,而非 “00”,所以查询的时候如果没有考虑空格,那么就会出现和预期不一致的现象,一些奇怪的bug由此而生!
在前面的笔记中提到过,数据库字段设计为char类型是历史债务,现在不适合改类型,影响面太广。因此这里的做法是查询的时候使用rtrim把空格去掉,这样DataTable没有空格后,数据就可以正常插入了。
小结:空格防不胜防,只能自己多加小心了。