C#使用SqlTransaction事務回滾與SqlBulkCopy批量插入數據


C#中批量處理數據,有時候因為一條記錄導致整個批量處理失敗。這時候肯能會導致數據不全等問題,這時候我們可以使用SqlTransaction來進行事務回滾,即是要么全部成功要么全部不成功。如下代碼

  //測試事務回滾
        public static string GetMsgBySJ()
        {
            var msg = "";
            SqlConnection conn = new SqlConnection(connStr);
            SqlCommand cmd = conn.CreateCommand();
            conn.Open();//打開之后開啟事務
            SqlTransaction tran = conn.BeginTransaction();//開啟事務
            cmd.Transaction = tran;//將事務應用於CMD
            try
            {
                cmd.CommandText = " INSERT into t_student VALUES ('huage1','11','男神') ";
                cmd.ExecuteNonQuery();
                cmd.CommandText = " INSERT into t_student VALUES ('huage','11','女神','') ";
                cmd.ExecuteNonQuery();
                tran.Commit();//提交事務(不提交不會回滾錯誤)
                msg = "插入成功";
            }
            catch (Exception ex)
            {
                tran.Rollback();
                msg = "插入失敗,事物回滾";
            }
            finally
            {
                conn.Close();
                conn.Dispose();
                cmd.Dispose();
                tran.Dispose();
            }
            return msg;
        }

上面測試代碼,INSERT into t_student VALUES ('huage1','11','男神')這條記錄其實已經插入數據庫,但是因為下條語句操作失敗導致插入數據錯誤,這時候這個Rollback()函數會將數據庫表中的數據還原到操作表之前,

也就是說第一條執行成功的語句也會被刪掉(如果有自增Id的話,可以去數據庫中再插入數據查看Id是否不再連續)。

  下面在介紹一個SqlBulkCopy批量快速插入數據的方法,如果數據量大的話用循環語句進行數據的插入那肯定會使程序變的巨慢,這時候使用SqlBulkCopy來進行插入數據的話就會顯得很優越

下面以插入10000行數據做測試。

 //批量將數據導入目的表
        public static void DtDrTable(DataTable dt, string tableName)
        {
            try
            {
                //這邊可以使用事務回滾機制
                SqlBulkCopy bcp = new SqlBulkCopy(connStr);
                //指定目標數據庫的表名
                bcp.DestinationTableName = tableName;
                //每一批次的行數
                bcp.BatchSize = 100 * 100;
                //建立數據源表字段和目標表中的列之間的映射
                //----既然dt的列名需要與表明完全一致,直接循環dt的列即可----//
                foreach (DataColumn dc in dt.Columns)
                    bcp.ColumnMappings.Add(dc.ColumnName, dc.ColumnName);
                //寫入數據庫表 dt 是數據源DataTable
                bcp.WriteToServer(dt);
                //關閉SqlBulkCopy實例
                bcp.Close();
            }
            catch (Exception ex)
            {                
                throw ex;
            }
        }

使用SqlBulkCopy進行數據插入的時候,要使得DataTable中的列名,類型與數據庫表要完全一致(除了自增Id),下面是調用上面方法的例子

 Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            DataTable dtTemp = new DataTable();
            DataColumn[] columns = new DataColumn[]
            {
             new DataColumn("Name",typeof(string)),
             new DataColumn("Age",typeof(Int32)),
             new DataColumn("Sex",typeof(string)),
             new DataColumn("test",typeof(string)),
            };
            dtTemp.Columns.AddRange(columns);
            //構造一個10000行的DataTable
            for (var i = 0; i < 100 * 100; i++)
            {
                var n_row = dtTemp.NewRow();
                var tt = i + 1;
                n_row["Name"] = "Name" + tt;
                n_row["Age"] = tt;
                n_row["Sex"] = "Sex" + tt;
                n_row["test"] = "test" + tt;
                dtTemp.Rows.Add(n_row);
            }
            stopWatch.Stop();
            Console.WriteLine("構造一萬行的Datatable所需時間:" + stopWatch.Elapsed);

            Stopwatch stopwatcj = new Stopwatch();
            stopwatcj.Start();
            DtDrTable(dtTemp, "t_student");
            stopwatcj.Stop();
            Console.WriteLine("10000行數據批量插入表中所需時間:" + stopwatcj.Elapsed);

執行上述測試代碼的控制台程序,結果如下:

 

甚至一秒時間都沒到,在內存中構建內存表雖然更快,但是很費內存

  下面展示一個可回滾的批量插入(暫時未測試)

 

        //批量將數據導入目的表可回滾
        public static void TranBatchImportData(DataTable dt, string tableName)
        {
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                SqlTransaction tran = conn.BeginTransaction();
                using (SqlBulkCopy sqlBC = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, tran))
                {
                    sqlBC.BatchSize = 100 * 100;
                    //sqlBC.BulkCopyTimeout = 60;//超時之前操作完成所允許的秒數
                    sqlBC.DestinationTableName = tableName;
                    foreach (DataColumn dc in dt.Columns)
                        sqlBC.ColumnMappings.Add(dc.ColumnName, dc.ColumnName);

                    sqlBC.WriteToServer(dt);
                    tran.Commit();
                }
            }
        }

 之前在博問中有人提問:有三個不同操作的數據的方法,又不能修改方法,調用的時候能不能加個相當於全局事務鎖,就是這三個方法要么全部執行,要么你全部不執行。 

 下面有人提供的解決方案,使用 System.Transactions.TransactionScope ,這個事務可以實現。

然后我就Mark了一波,寫了個例子看看。

  static void Test()
        {
            //var transactionOption = new TransactionOptions();
            ////設置事務隔離級別
            //transactionOption.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
            //// 設置事務超時時間為60秒
            //transactionOption.Timeout = new TimeSpan(0, 0, 60);
            using (System.Transactions.TransactionScope scope =
                new TransactionScope(TransactionScopeOption.Required,
                    new TransactionOptions
                    {
                        IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
                        Timeout = new TimeSpan(0, 0, 120)
                    }))
            {
                try
                {
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789');");
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789');");
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789');");
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789');");
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789');");
                    ExecuteSql("delete from  [user] where id=5;");
                    ExecuteSql(
                        "INSERT INTO [user]([id], [name], [age], [password]) VALUES (5, N'zhaoliu', 17, '789',123);");//錯誤方法
                    scope.Complete();
                }
                catch (Exception ex)
                {

                    Console.WriteLine(ex);
                }
            }
        }

  的確是可以實現集中方法之間實現事務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM