SqlDataAdapter 更新插入 與 InsertBulkCopy
近日做項目,涉及多個數據庫多個表的關聯更新,因數據量巨大,逐條更新也很費時。於是就想用SqlDataAdapter 一次提交一批數據過去。以下是自己經歷的坑:
1. table Merge 部分
DataTable dtCBBill = DbHelper.ExecuteDataAdapter(SqlHelper.cbBill, pars, strConnOldCBBill); DataTable dtMember = DbHelper.ExecuteDataAdapter(SqlHelper.accounts_m, null, strConnMember); DataTable dtUser= DbHelper.ExecuteDataAdapter(SqlHelper.accounts_u2, null, strConnWeb); //篩選出有賬單的用戶 DataTable dtM = (from c in dtMember.AsEnumerable() join r in dtCBBill.AsEnumerable() on c["f_accounts"] equals r["f_accounts"] select c).CopyToDataTable(); DataTable dtU = (fromc in dtUser.AsEnumerable() join r in dtCBBill.AsEnumerable() on c["f_accounts"] equals r["f_accounts"] select c).CopyToDataTable(); //設置主鍵加快合並速度 SetPrimaryKey(dtCBBill); SetPrimaryKey(dtM); SetPrimaryKey(dtU); MergeTable(dtM, dtCBBill); MergeTable(dtU, dtCBBill);
。。。。dtContribution
Merge提示<target>.f_CBMoney 和 <source>.f_CBMoney 的屬性沖突: DataType 屬性不匹配。幾經調試,發現,源dtCBBill的一個字段是decimal,而對應合並的目的表dtContribution相應字段是int 類型。修改sql語句,cast(某字段 as int) 解決此類異常。網上有說
在調用dt1.Merge(dt2)的時候,由於兩個serverid字段類型不一致,一個int32,一個int64,導致無法Merge。用importRow的方式就可以合並了:
private DataTable MergeTable(DataTable dest, DataTable source) { DataRow[] sourceRows = source.Select(); for (int i = 0; i < sourceRows.Length; i++) { dest.ImportRow(sourceRows[i]); } return dest; }
這種方法需要改動代碼多,就沒有嘗試。
2. InsertBulkCopy 部分
public static int InsertBulkCopy(string connectionString, DataTable dt,string destTable) { using (SqlConnection Connection = new SqlConnection(connectionString)) { Connection.Open(); using (SqlTransaction transaction = Connection.BeginTransaction()) { using (SqlBulkCopy sqlbulkcopy = new SqlBulkCopy((SqlConnection)Connection, SqlBulkCopyOptions.KeepIdentity, transaction)) { sqlbulkcopy.DestinationTableName = destTable; for (int i = 0; i < dt.Columns.Count; i++) { sqlbulkcopy.ColumnMappings.Add(dt.Columns[i].ColumnName, dt.Columns[i].ColumnName); } sqlbulkcopy.BatchSize = 10000; try { sqlbulkcopy.WriteToServer(dt); transaction.Commit(); return 1; } catch(Exception ex) { transaction.Rollback(); return 0; } } } } }
必須要保證 source table 與 destination table 字段完全一致,包括字段的大小寫。因為大小寫不一樣,如 f_employee 與 f_Employee,直接會導致插入失敗。
3. 最重要的 SqlDataAdapter
SqlDataAdapter adapter = new SqlDataAdapter(); SqlConnection conn = new SqlConnection(strConnCash); adapter.SelectCommand = new SqlCommand(SqlHelper.selCmdText, conn); SqlParameter parameter1 = adapter.SelectCommand.Parameters.Add("@f_date", SqlDbType.DateTime); parameter1.Value = date; DataTable dtContri = new DataTable(); adapter.Fill(dtContri); adapter.UpdateBatchSize = 1000; adapter.UpdateCommand = new SqlCommand(SqlHelper.updCmdText, conn); adapter.UpdateCommand.Parameters.Add("@f_CBMoney", SqlDbType.Int, 4, "f_CBMoney"); adapter.UpdateCommand.Parameters.Add("@f_CBMresult", SqlDbType.Int, 4, "f_CBMresult"); adapter.UpdateCommand.Parameters.Add("@f_CBNum", SqlDbType.Int, 4, "f_CBNum"); SqlParameter[] paramters = new SqlParameter[] { new SqlParameter("@f_date",SqlDbType.DateTime,8,"f_date"), new SqlParameter("@f_accounts",SqlDbType.VarChar,20,"f_accounts") }; paramters[0].SourceVersion = DataRowVersion.Original; paramters[1].SourceVersion = DataRowVersion.Original; adapter.UpdateCommand.Parameters.AddRange(paramters); adapter.UpdateCommand.UpdatedRowSource = UpdateRowSource.None; adapter.InsertCommand = new SqlCommand(SqlHelper.insCmdText, conn); adapter.InsertCommand.Parameters.Add("@f_accounts", SqlDbType.VarChar, 20, "f_accounts"); adapter.InsertCommand.Parameters.Add("@f_date", SqlDbType.DateTime, 4, "f_date"); adapter.InsertCommand.Parameters.Add("@f_CBMoney", SqlDbType.Int, 4, "f_CBMoney"); adapter.InsertCommand.Parameters.Add("@f_CBMresult", SqlDbType.Int, 4, "f_CBMresult"); adapter.InsertCommand.Parameters.Add("@f_CBNum", SqlDbType.Int, 4, "f_CBNum"); adapter.InsertCommand.Parameters.Add("@f_StupeSurplus", SqlDbType.Int, 4, "f_StupeSurplus"); adapter.InsertCommand.Parameters.Add("@f_status", SqlDbType.TinyInt, 1, "f_status"); adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.None; //將ds數據同步到dtContri表 SetPrimaryKey(dtContri); MergeTableManual( dtCBBill,ref dtContri); //執行更新插入 int r = adapter.Update(dtContri); log.Info(site + " CB update&insert: " + r);
/// <summary> /// 設置主鍵 /// </summary> /// <param name="dt"></param> private void SetPrimaryKey(DataTable dt) { dt.PrimaryKey = new DataColumn[] { dt.Columns["f_accounts"] }; } /// <summary> /// 合並表數據 /// </summary> /// <param name="source"></param> /// <param name="target"></param> private void MergeTable(DataTable source, DataTable target, bool isAddSchema = true) { target.Merge(source, false, isAddSchema ? MissingSchemaAction.Add : MissingSchemaAction.Ignore); } /// <summary> /// 手動合並表數據 /// </summary> /// <param name="source"></param> /// <param name="target"></param> private void MergeTableManual(DataTable source,ref DataTable target) { foreach (DataRow item in source.Rows) { var row = target.Rows.Find(item["f_accounts"]); if (row != null) { row["f_CBMoney"] = item["f_CBMoney"]; row["f_CBMresult"] = item["f_CBMresult"]; row["f_CBNum"] = item["f_CBNum"]; } else { var newRow = target.NewRow(); newRow["f_accounts"] = item["f_accounts"]; newRow["f_date"] = item["f_date"]; newRow["f_CBMoney"] = item["f_CBMoney"]; newRow["f_CBMresult"] = item["f_CBMresult"]; newRow["f_CBNum"] = item["f_CBNum"]; newRow["f_StupeSurplus"] = item["f_StupeSurplus"]; newRow["f_status"] = item["f_status"]; target.Rows.Add(newRow); } } }
這其中坑最多。按照 https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/updating-data-sources-with-dataadapters 這篇最有價值的文章,發現日誌記錄更新數目總是為0。 然後自己又做個demo,一點一點拆開研究,dataTable.SaveChanges 只是把源表最近的更新同步到離線表中,不可用。但離線表,取其中的一DataRow,改變下數據,甚至原數據再更新都能Update成功,最後通過打印RowState屬性值,得到,用dataTable1.Merge(dataTable2,...)方式合並來的數據,RowState 為unchanged。遂手動Merge。運行,OK。終於搞定了。
SqlDataAdapter 更新插入 與 InsertBulkCopy