• 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);
                    }
                }
            }
    

      的确是可以实现集中方法之间实现事务。

  • 相关阅读:
    codeforces 1060 B
    codeforces 1060 A
    牛客 国庆七天乐 day1 L
    BZOJ 1087: [SCOI2005]互不侵犯King
    codeforces 792CDivide by Three(两种方法:模拟、动态规划
    codeforces 797C Minimal string
    codeforces 110E Lucky Tree
    codeforces 798D
    2017福建省赛 FZU2272~2283
    Android -- Looper、Handler、MessageQueue等类之间关系的序列图
  • 原文地址:https://www.cnblogs.com/huage-1234/p/8079146.html
Copyright © 2020-2023  润新知