最近有个需求,将excel中的数据大批量导入到Sql Server中,方法很简单,使用SqlBulkCopy
即可,网上介绍的方法也比较多,这里只是介绍一下对于数据的异常处理。
由于项目是放在服务中运行,出现错误的数据希望只是希望将数据单独保存下来,不去影响其他数据的导入,网上找了一圈,没有找到合适的办法,只好自己仔细研究了一下,然后看到了SqlRowsCopied事件,MSDN中解释:在每次处理完 NotifyAfter 属性指定的行数时发生。如果将NotifyAfter设置为1的话,每次都记录一下当前执行到的行数,当出现异常的时候,可以定位到出错的行数。当知道了出错的行数,剩下的事情就很好办了,将出错的行数据单独拿出另存处理,计算一下已经正确导入到数据库中的数据行,移除出现,将剩下的数据继续导入。
1 /// <summary> 2 /// 导入数据,使用SqlBulkCopy 3 /// </summary> 4 public class SqlServerHelp : IDisposable 5 { 6 SqlConnection conn = null; 7 SqlBulkCopy bulkCopy = null; 8 9 private static string GetConnectionString() 10 { 11 return ConfigurationManager.AppSettings["SqlConnection"]; 12 } 13 14 int batchSize = 1000;//批处理数据条数 15 int bulkCopyTimeout = 3600; 16 17 public SqlServerHelp() 18 { 19 string connectionString = GetConnectionString(); 20 conn = new SqlConnection(connectionString); 21 Open(); 22 bulkCopy = new SqlBulkCopy(conn); 23 bulkCopy.BatchSize = batchSize; 24 bulkCopy.BulkCopyTimeout = bulkCopyTimeout; 25 bulkCopy.NotifyAfter = 1; 26 bulkCopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(SqlRowsCopied); 27 } 28 29 public void Dispose() 30 { 31 this.Close(); 32 } 33 34 public void Close() 35 { 36 data = null; 37 if (conn != null && conn.State != ConnectionState.Closed) 38 conn.Close(); 39 if (bulkCopy != null) 40 bulkCopy.Close(); 41 } 42 43 public void Import(BaseImportData importdata) 44 { 45 if (importdata.CanImport) 46 { 47 if (importdata.DtSource == null || importdata.DtSource.Rows.Count == 0) 48 { 49 } 50 else 51 { 52 #region 53 bulkCopy.DestinationTableName = importdata.TableName; 54 bulkCopy.ColumnMappings.Clear(); 55 56 foreach (ObjectColumn column in importdata.columns) 57 { 58 bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping( 59 column.ExcelColumn, column.SqlServerColumn)); 60 } 61 62 foreach (ObjectColumn column in importdata.defaultColumns) 63 { 64 bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping( 65 column.ExcelColumn, column.SqlServerColumn)); 66 } 67 68 try 69 { 70 data = importdata; 71 rowsCopiedId = 0; 72 indexRe = 0; 73 Open(); 74 bulkCopy.WriteToServer(importdata.DtSource); 75 } 76 catch (SqlException ex) 77 { 78 DealSqlException(importdata, ex); 79 } 80 catch (InvalidOperationException ex) 81 { 82 DealExceptionNone(importdata, ex); 83 } 84 catch (Exception ex) 85 { 86 DealException(importdata, ex); 87 } 88 #endregion 89 } 90 } 91 } 92 93 /// <summary> 94 /// 数据导入完成之后导入日志以及错误信息 95 /// </summary> 96 /// <param name="importdata"></param> 97 public void ImportLog(BaseImportData importdata) 98 { 99 data = importdata; 100 rowsCopiedId = 0; 101 indexRe = 0; 102 InsertImportLog(importdata); 103 104 foreach (ErrorInfo error in importdata.errorList) 105 { 106 InsertErrorInfo(error); 107 } 108 } 109 110 private void ImportError(BaseImportData importdata) 111 { 112 try 113 { 114 data = importdata; 115 rowsCopiedId = 0; 116 indexRe = 0; 117 Open(); 118 bulkCopy.WriteToServer(importdata.DtSource); 119 } 120 catch (SqlException ex) 121 { 122 DealSqlException(importdata, ex); 123 } 124 catch (InvalidOperationException ex) 125 { 126 DealExceptionNone(importdata, ex); 127 } 128 catch (Exception ex) 129 { 130 DealException(importdata, ex); 131 } 132 } 133 134 private void DealExceptionNone(BaseImportData importdata, Exception ex) 135 { 136 //错误行索引 137 int errorIndex = Convert.ToInt32(rowsCopiedId); 138 139 int succIndex = (errorIndex / batchSize) * batchSize;//成功导入到数据库中的数据最大索引 140 //移除掉导入成功的数据 141 for (int i = 0; i < succIndex; i++) 142 { 143 importdata.DtSource.Rows.RemoveAt(0); 144 } 145 rowsCopiedId = 0; 146 147 Logger.Info("插入数据出现异常:" + ex.GetType() + ex.Message); 148 149 this.ImportError(importdata); 150 } 151 152 /// <summary> 153 /// 如果出现异常,进行处理 154 /// </summary> 155 private void DealSqlException(BaseImportData importdata, SqlException ex) 156 { 157 switch (ex.Number) 158 { 159 case -1: 160 case -2: 161 case 2: 162 case 53: 163 //数据库连接不上,超时等,隔一段时间后重新连接 164 //错误行索引 165 int errorIndex = Convert.ToInt32(rowsCopiedId); 166 167 int succIndex = (errorIndex / batchSize) * batchSize;//成功导入到数据库中的数据最大索引 168 //移除掉导入成功的数据 169 for (int i = 0; i < succIndex; i++) 170 { 171 importdata.DtSource.Rows.RemoveAt(0); 172 } 173 rowsCopiedId = 0; 174 175 Logger.Info("导入数据异常:" + ex.Message); 176 177 System.Threading.Thread.Sleep(600000); 178 179 this.ImportError(importdata); 180 break; 181 case 10053: 182 case 64: 183 DealExceptionNone(importdata, ex); 184 break; 185 default: 186 //其余的异常认为是数据行出错 187 DealException(importdata, ex); 188 break; 189 } 190 } 191 192 /// <summary> 193 /// 如果出现异常,进行处理 194 /// </summary> 195 private void DealException(BaseImportData importdata, Exception ex) 196 { 197 importdata.ImportResult = "存在错误数据"; 198 199 //错误行索引 200 int errorIndex = Convert.ToInt32(rowsCopiedId); 201 int succIndex = (errorIndex / batchSize) * batchSize;//成功导入到数据库中的数据最大索引 202 //long errorNum = rowsNum + errorIndex + 1; 203 204 ErrorInfo error = new ErrorInfo(importdata); 205 if (ex is SqlException) 206 { 207 error.ErrorDesc = ex.GetType() + ((SqlException)ex).Number.ToString() + ex.Message; 208 } 209 else 210 error.ErrorDesc = ex.GetType() + ex.Message; 211 212 error.SetContent(importdata.DtSource.Rows[errorIndex]); 213 214 //error.Remark = importdata.DtSource.TableName.Replace("'", "").Replace("$", "") + "中,第" + ( 215 // errorNum + 1).ToString() + "行"; 216 error.Remark = importdata.DtSource.TableName.Replace("'", "").Replace("$", "") + "中,第" + ( 217 importdata.DtSource.Rows[errorIndex]["ExcelRowNum"]).ToString() + "行"; 218 importdata.errorList.Add(error); 219 220 //rowsNum = rowsNum + succIndex + 1; 221 222 Logger.Error("存在错误数据:" + error.Remark + error.ErrorDesc);//记录异常数据 223 224 importdata.DtSource.Rows.RemoveAt(errorIndex);//删除异常数据 225 226 for (int i = 0; i < succIndex; i++) 227 { 228 importdata.DtSource.Rows.RemoveAt(0); 229 } 230 231 rowsCopiedId = 0; 232 this.ImportError(importdata); 233 } 234 235 //当前批量复制操作期间复制的行数 236 long rowsCopiedId = 0; 237 //成功导入到数据库中行数 238 //long rowsNum = 0; 239 long indexRe = 0;//已经批量执行到数据库中的次数 240 BaseImportData data; 241 private void SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e) 242 { 243 //错误行索引 244 int errorIndex = Convert.ToInt32(rowsCopiedId); 245 int succIndex = (errorIndex / batchSize) * batchSize;//成功导入到数据库中的数据最大索引 246 247 rowsCopiedId = e.RowsCopied; 248 if ((e.RowsCopied -1) / batchSize > indexRe) 249 { 250 Logger.Debug("已经成功导入" + succIndex + "行,excel中第" + data.DtSource.Rows[succIndex]["ExcelRowNum"].ToString() + "行"); 251 indexRe++; 252 } 253 else if (e.RowsCopied == data.DtSource.Rows.Count) 254 { 255 Logger.Debug("已经成功导入" + e.RowsCopied + "行,excel中第" + data.DtSource.Rows[succIndex]["ExcelRowNum"].ToString() + "行"); 256 indexRe++; 257 } 258 } 259 260 /// <summary> 261 /// 插入错误信息 262 /// </summary> 263 public void InsertErrorInfo(ErrorInfo info) 264 { 265 } 266 267 /// <summary> 268 /// 插入导入日志 269 /// </summary> 270 public void InsertImportLog(BaseImportData importdata) 271 { 272 273 } 274 275 /// <summary> 276 /// 修改导入日志 277 /// </summary> 278 public void UpdateImportLog(BaseImportData importdata) 279 { 280 281 } 282 283 public DataTable GetImportLog(string _dataType) 284 { 285 return null; 286 } 287 288 289 public void Open() 290 { 291 if (conn.State == ConnectionState.Closed) 292 { 293 try 294 { 295 conn.Open(); 296 } 297 catch (SqlException ex) 298 { 299 Logger.Debug("打开数据库连接异常:" + ex.GetType() + ex.Number + ex.Message); 300 System.Threading.Thread.Sleep(600000); 301 this.Open(); 302 } 303 catch (Exception ex) 304 { 305 Logger.Debug("打开数据库连接异常:" + ex.GetType() + ex.Message); 306 System.Threading.Thread.Sleep(600000); 307 this.Open(); 308 } 309 } 310 } 311 312 313 private DataTable QueryTable(string sql) 314 { 315 Open(); 316 SqlCommand comm = new SqlCommand(); 317 comm.Connection = conn; 318 comm.CommandType = CommandType.Text; 319 try 320 { 321 comm.CommandText = sql; 322 DataSet ds = new DataSet(); 323 SqlDataAdapter da = new SqlDataAdapter(comm); 324 da.Fill(ds); 325 if (ds.Tables.Count > 0) 326 return ds.Tables[0]; 327 } 328 catch (SqlException ex) 329 { 330 switch (ex.Number) 331 { 332 case -1: 333 case -2: 334 case 2: 335 case 53: 336 case 10053: 337 case 64: 338 Logger.Debug("网络故障:执行sql查询出错:" + ex.GetType() + ex.Message); 339 return this.QueryTable(sql); 340 default: 341 throw; 342 } 343 } 344 catch (InvalidOperationException ex) 345 { 346 Logger.Debug("网络故障:执行sql出错:" + ex.GetType() + ex.Message); 347 return this.QueryTable(sql); 348 } 349 catch (Exception ex) 350 { 351 throw; 352 } 353 return null; 354 } 355 356 private void ExecuteNonQuery(string sql) 357 { 358 Open(); 359 SqlCommand comm = new SqlCommand(); 360 comm.Connection = conn; 361 comm.CommandType = CommandType.Text; 362 try 363 { 364 comm.CommandText = sql; 365 comm.ExecuteNonQuery(); 366 } 367 catch (SqlException ex) 368 { 369 switch (ex.Number) 370 { 371 case -1: 372 case -2: 373 case 2: 374 case 53: 375 case 10053: 376 case 64: 377 Logger.Debug("网络故障:执行sql出错:" + ex.GetType() + ex.Message); 378 this.ExecuteNonQuery(sql); 379 break; 380 default: 381 throw; 382 } 383 } 384 catch (InvalidOperationException ex) 385 { 386 Logger.Debug("网络故障:执行sql出错:" + ex.GetType() + ex.Message); 387 this.ExecuteNonQuery(sql); 388 } 389 catch (Exception ex) 390 { 391 throw; 392 } 393 } 394 395 }
第一次发文,写的比较散乱,希望可以给有相同需求的人一些启示。