【问题标题】:If insert fails for one record delete all previously inserted row, is is possible in a transaction?如果一条记录插入失败,删除所有先前插入的行,是否可以在事务中?
【发布时间】:2013-12-16 14:36:33
【问题描述】:

我的代码中有 SaveListOfObjects 之类的方法,我在 foreach 循环中执行,然后将 insert 记录到 SQL Server。

当我插入的数据没有错误时,它工作得很好。但是如果发生错误,那么只有有效的数据会被插入到 SQL 中。

我想做以下事情:

  1. 仅在整个数据有效且插入时出现一个错误的情况下插入所有记录。
  2. 删除所有以前保存在 SQL 中的数据。

所以,我已经尝试过使用TransactionScopeSqlTransaction 类,甚至使用SQL TRANSACTION,但我唯一能做的就是插入有效数据并省略无效数据。

现在,就我在网上搜索,我发现并行交易是不可能的。此外,SQL 具有禁止并行任务的隔离级别。

是否有任何可能的方法来完成 SQL 中的插入,例如 ALL 或 NOTHING?

更新:

我的代码如下:

public int Ramiz_SavePack(IPacking pack)
    {
        using (var conn = (SqlConnection)connector.GetConnection())
        {
            conn.Open();
            SqlTransaction transaction;
            var comm = (SqlCommand)connector.GetCommand("Ramiz_Pack_Save");
            comm.CommandType = CommandType.StoredProcedure;
            transaction = conn.BeginTransaction();
            comm.Transaction = transaction;
            int rowNum = 0;


            try
            {
                if (!string.IsNullOrEmpty(pack.BrojKolete))
                    comm.Parameters.Add("@BrojKolete", SqlDbType.NVarChar).Value = pack.BrojKolete;
                else
                    comm.Parameters.Add("@BrojKolete", SqlDbType.NVarChar).Value = DBNull.Value;
                comm.Parameters.Add("@Bosanski", SqlDbType.NVarChar).Value = pack.Bosanski;
                comm.Parameters.Add("@Kom", SqlDbType.Float).Value = pack.Kom;
                comm.Parameters.Add("@Vrsta", SqlDbType.NVarChar).Value = pack.Vrsta;
                comm.Parameters.Add("@Datum", SqlDbType.Date).Value = pack.Datum;
                comm.Parameters.Add("@BrojKamiona", SqlDbType.Int).Value = pack.BrojKamiona;

                 rowNum = comm.ExecuteNonQuery();
                transaction.Commit();

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                try
                {
                    conn.Close();
                    transaction.Rollback();
                }
                catch (Exception ex2)
                {
                    Console.WriteLine(ex2.Message);
                }

            }
            return rowNum;

        }
    }

并在这个内部调用这个方法:

 var pack = new Pack();
        for (int i = 1; i < lastRow; i++)
        {
            pack.Ramiz_SavePack(new Packing
            {
                BrojKolete = Convert.ToString(brojKoleteRange.Offset[i, 0].Value2),
                Bosanski = Convert.ToString(nazivArtiklaRange.Offset[i, 0].Value2),
                Kom = Convert.ToDouble(komRange.Offset[i, 0].Value2),
                Vrsta = Convert.ToString(vrstaRange.Offset[i, 0].Value2),
                BrojKamiona = int.Parse(ddlBrojKamiona.SelectedItem.Value),
                Datum = Convert.ToDateTime(txtDate.Text)
            });
            pnlMessageSuccess.Visible = true;

        }

【问题讨论】:

  • 验证的条件是什么?你能分享一些你的代码吗?
  • 请显示您的代码,因为如果我理解正确,正确的方法就是使用 SqlTransaction 或 TransactionScope 类。
  • 你是否在事务范围内使用回滚?
  • 您正在为每个项目创建单独的事务 - 您需要将循环包装在事务中或将项目集合发送到 Ramiz_SavePack 方法。如果你回滚,你只是回滚一个事务。您需要在同一个事务中完成所有数据库工作
  • @Charleh。你的意思是我应该将 List 传递给 Ramiz_SavePack 方法并在里面尝试关闭我的 foreach 循环?

标签: c# sql-server insert rollback


【解决方案1】:

对数据输入对象进行约束更容易,例如约束某个文本框只接受数字或任何你想要的。 作为另一个想法 .. 在将数据查询到数据库之前尝试检查数据准确性

【讨论】:

    【解决方案2】:

    一种方法是通过将列表中的每个对象映射到 DataTable 的一行来从列表中创建一个 DataTable。然后,您可以遍历 DataTable 行以执行插入操作,并将此代码块包含在 Transaction 中。这将允许您防止输入部分数据。

    您可以参考this link 了解如何将列表转换为 DataTable。

    【讨论】:

      【解决方案3】:

      在我看来,您正在循环并为每个对象调用 save 方法。如果交易存在于该循环周围,这不是问题,但事实并非如此。您正在分别回滚/提交每个对象

      您需要创建一个要保存的对象列表并将其发送到 save 方法,或者创建一个包装循环的事务,例如:

      var list = new List<Pack>();
      
      foreach(<your loop>)
      {
         list.Add(new Pack(<some values>);
      }
      
      SavePacks(list);
      
      void SavePacks(IList<Pack> items) 
      {
          <create connection and transaction here and loop through inserting each item, rollback on error>
      }
      

      using(var tran = new SqlTransaction()) 
      {
          <Do save logic here for all items and rollback if something went wrong>
      }
      

      【讨论】:

      • 它应该可以工作 - 尝试修改您的代码,如果它不起作用,请使用新代码更新您的问题。如果你按照上面的方法做了,但没有成功,那么你可能做错了什么(也可能是一些很小的事情!)
      • 现在我正在尝试修改它。如果不成功,我将发布我的新代码。谢谢
      • 非常感谢。我终于以正确的方式做到了。另外,谢谢大家的帮助。我决定选择这个作为正确答案,因为它最适合我的代码。 @Steve 还提供了代码的答案,但这需要我的代码进行许多更改,所以我决定使用这个。
      • 我尊重您的决定,但我想指出,通过这种方式,您可以在对象上循环两次以执行插入。这不是最有效的方法。 (当然,我的答案都不是最有效的方法,Common Table Expression 可以提供最好的方法,但这确实是一个巨大的代码差异)
      【解决方案4】:

      您的问题在于您打开和关闭数据库中写入的每个对象的事务。当然,这意味着当您遇到数据问题时,先前正确的数据已经写入数据库并提交。所以无法回滚。

      解决方案是在你的数据插入方法之外打开连接和事务,并在方法内部传递这些对象实例。

      public int Ramiz_SavePack(IPacking pack, SqlConnection conn, SqlTransaction transaction)
      {
          var comm = (SqlCommand)connector.GetCommand("Ramiz_Pack_Save");
          comm.Connection = conn;
          comm.CommandType = CommandType.StoredProcedure;
          comm.Transaction = transaction;
          ....
      }
      
      
      .....
      try
      {    
          using (var conn = (SqlConnection)connector.GetConnection())
          {
              conn.Open();
              SqlTransaction transaction = conn.BeginTransaction();
              var pack = new Pack();
              for (int i = 1; i < lastRow; i++)
              {
                 pack.Ramiz_SavePack(new Packing
                 {
                     BrojKolete = Convert.ToString(brojKoleteRange.Offset[i, 0].Value2),
                     Bosanski = Convert.ToString(nazivArtiklaRange.Offset[i, 0].Value2),
                     Kom = Convert.ToDouble(komRange.Offset[i, 0].Value2),
                     Vrsta = Convert.ToString(vrstaRange.Offset[i, 0].Value2),
                     BrojKamiona = int.Parse(ddlBrojKamiona.SelectedItem.Value),
                     Datum = Convert.ToDateTime(txtDate.Text)
                 }, conn, transaction);
              }
              pnlMessageSuccess.Visible = true;
           }
         }
      }
      catch (Exception ex)
      {
          Console.WriteLine(ex.Message);
          transaction.Rollback();
      }
      

      这样插入数据的方法,当它失败时,将引发一个异常,被打开连接和事务的调用代码捕获。这个调用代码然后可以调用回滚方法来撤消到目前为止插入的每个对象。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-03
        • 1970-01-01
        • 1970-01-01
        • 2016-12-29
        • 1970-01-01
        相关资源
        最近更新 更多