最近公司需要优化导入的问题,由于之前使用的方式是生成 Insert 语句插入数据库,数据量小的时候还行,但是随着发展数据量渐渐大了,之前的方法性能就跟不上了,于是发现了 SqlBulkCopy 这个类。
    使用 SqlBulkCopy 类只能向 SQL Server 表写入数据。但是,数据源不限于 SQL Server;可以使用任何数据源,只要数据可加载到 DataTable 实例或可使用 IDataReader 实例读取数据。

    1. public class Conn
    2. {
    3. private static string StrConn
    4. {
    5. get
    6. {
    7. return ConfigurationManager.ConnectionStrings["StrConn"].ToString();
    8. //return ConfigurationManager.AppSettings["StrConn"].ToString();
    9. }
    10. }
    11. public static SqlConnection SqlConn
    12. {
    13. get
    14. {
    15. return new SqlConnection(StrConn);
    16. }
    17. }
    18. }
    19. public class SqlHelper
    20. {
    21. public DataTable GetDataTable(string sql)
    22. {
    23. DataTable dt = new DataTable();
    24. SqlConnection conn = null;
    25. SqlDataAdapter sda = null;
    26. try
    27. {
    28. conn = Conn.SqlConn;
    29. sda = new SqlDataAdapter(sql, conn);
    30. conn.Open();
    31. sda.Fill(dt);
    32. }
    33. catch (Exception ex)
    34. {
    35. }
    36. finally
    37. {
    38. if (conn != null)
    39. {
    40. conn.Close();
    41. conn.Dispose();
    42. }
    43. if (sda != null)
    44. {
    45. sda.Dispose();
    46. }
    47. }
    48. return dt;
    49. }
    50. public DataSet GetDataSet(string sql)
    51. {
    52. DataSet ds = new DataSet();
    53. SqlConnection conn = null;
    54. SqlDataAdapter sda = null;
    55. try
    56. {
    57. conn = Conn.SqlConn;
    58. sda = new SqlDataAdapter(sql, conn);
    59. conn.Open();
    60. sda.Fill(ds);
    61. }
    62. catch (Exception ex)
    63. {
    64. }
    65. finally
    66. {
    67. if (conn != null)
    68. {
    69. conn.Close();
    70. conn.Dispose();
    71. }
    72. if (sda != null)
    73. {
    74. sda.Dispose();
    75. }
    76. }
    77. return ds;
    78. }
    79. /// <summary>
    80. /// 使用事务插入方法
    81. /// </summary>
    82. /// <param name="dt">源数据</param>
    83. /// <param name="tableName">目标表名</param>
    84. public void InsertO(DataTable dt, string tableName)
    85. {
    86. using (SqlConnection conn = Conn.SqlConn)
    87. {
    88. using (SqlBulkCopy sqlBuleCopy = new SqlBulkCopy(conn.ConnectionString,
    89. SqlBulkCopyOptions.CheckConstraints
    90. | SqlBulkCopyOptions.Default
    91. | SqlBulkCopyOptions.UseInternalTransaction))
    92. {
    93. try
    94. {
    95. //设置目标表名,即数据库表名
    96. sqlBuleCopy.DestinationTableName = tableName;
    97. //设置每一批次的行数,即达到指定的行数就插入一次数据库
    98. sqlBuleCopy.BatchSize = 100000;
    99. //设置超时之前完成的时间(秒)
    100. sqlBuleCopy.BulkCopyTimeout = 3600;
    101. for (int i = 0; i < dt.Columns.Count; i++)
    102. {
    103. //设置源数据列与目标表的列的映射关系,第一个参数为源数据列,第二个参数为目标表列
    104. sqlBuleCopy.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName);
    105. }
    106. sqlBuleCopy.WriteToServer(dt);
    107. }
    108. catch (Exception)
    109. {
    110. }
    111. }
    112. }
    113. }
    114. /// <summary>
    115. /// 未使用事务插入方法
    116. /// </summary>
    117. /// <param name="dt">源数据</param>
    118. /// <param name="tableName">目标表名</param>
    119. public void InsertT(DataTable dt, string tableName)
    120. {
    121. using (SqlConnection conn = Conn.SqlConn)
    122. {
    123. using (SqlBulkCopy sqlBuleCopy = new SqlBulkCopy(conn))
    124. {
    125. try
    126. {
    127. conn.Open();
    128. //设置目标表名,即数据库表名
    129. sqlBuleCopy.DestinationTableName = tableName;
    130. //设置每一批次的行数,即达到指定的行数就插入一次数据库
    131. sqlBuleCopy.BatchSize = 100000;
    132. //设置超时之前完成的时间(秒)
    133. sqlBuleCopy.BulkCopyTimeout = 3600;
    134. for (int i = 0; i < dt.Columns.Count; i++)
    135. {
    136. //设置源数据列与目标表的列的映射关系,第一个参数为源数据列,第二个参数为目标表列
    137. sqlBuleCopy.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName);
    138. }
    139. sqlBuleCopy.WriteToServer(dt);
    140. }
    141. catch (Exception)
    142. {
    143. conn.Close();
    144. conn.Dispose();
    145. }
    146. finally
    147. {
    148. conn.Close();
    149. conn.Dispose();
    150. }
    151. }
    152. }
    153. }
    154. }

    我的源数据是使用 Excel 导入的数据,导入的方法就不说了,不是这里的重点,之后我会专门总结一下 Excel 导入的方法。然后查询目标表需要插入数据的字段,修改源数据表的字段名和类型,然后调用批量插入的方法。

    1. protected void btnImport_Click(object sender, EventArgs e)
    2. {
    3. try
    4. {
    5. //获取导入的数据
    6. DataSet ds = BI.ExecleDs(savePath, "");
    7. if (ds != null && ds.Tables.Count > 0)
    8. {
    9. DataTable dt = ds.Tables[0];
    10. //查询目标表需要插入的字段
    11. string sql = " select U_No,U_Name,U_Pwd,P_Id from UserInfo ";
    12. DataTable dt1 = sqlhelper.GetDataTable(sql);
    13. if (dt1 != null)
    14. {
    15. for (int i = 0; i < dt1.Columns.Count; i++)
    16. {
    17. //修改源数据表的字段类型和字段名称
    18. dt.Columns[i].DataType = dt1.Columns[i].DataType;
    19. dt.Columns[i].ColumnMapping = dt1.Columns[i].ColumnMapping;
    20. dt.Columns[i].ColumnName = dt1.Columns[i].ColumnName;
    21. }
    22. sqlhelper.InsertO(dt, "UserInfo");
    23. }
    24. }
    25. }
    26. catch (Exception ex)
    27. {
    28. throw;
    29. }
    30. }

    以上这种修改数据类型的方法,如果碰到数据类型不一致并且 DataTable 有数据的时候,会报错,不能修改有数据的列的数据类型。(好像是废话,嘿嘿,没有数据和有数据时不能修改数据类型,这完全是没用的。)
    所以就有了下面的方法,先实例化一个新的 DataTable,然后复制目标表的架构,然后再把数据保存到新的 DataTable 中。

    1. protected void btnImport_Click(object sender, EventArgs e)
    2. {
    3. try
    4. {
    5. //获取导入的数据
    6. DataSet ds = BI.ExecleDs(savePath, "");
    7. if (ds != null && ds.Tables.Count > 0)
    8. {
    9. DataTable dt = ds.Tables[0];
    10. //查询目标表需要插入的字段
    11. string sql = " select U_No,U_Name,U_Pwd,P_Id from UserInfo ";
    12. DataTable dt1 = sqlhelper.GetDataTable(sql);
    13. DataTable dt2 = new DataTable();
    14. if (dt1 != null)
    15. {
    16. //复制目标表的架构
    17. dt2 = dt1.Clone();
    18. for (int i = 0; i < dt1.Rows.Count; i++)
    19. {
    20. DataRow dr = dt2.NewRow();
    21. dr = dt1.Rows[i];
    22. dt2.Rows.Add(dr.ItemArray);
    23. }
    24. sqlhelper.InsertO(dt2, "UserInfo");
    25. }
    26. }
    27. }
    28. catch (Exception ex)
    29. {
    30. throw;
    31. }
    32. }

    如果源数据表的列和目标表的列的顺序或列名不相同,那就必须使用 ColumnMappings.Add() 方法设置映射关系。