Если insert не удается для одной записи удалить все ранее вставленные строки, возможно ли это в транзакции?


У меня есть метод в моем коде, как SaveListOfObjects, который я выполняю внутри цикла foreach, а затем insert записывает в SQL Server.

Он отлично работает, когда нет ошибки в данных, которые я вставляю. Но если произошла ошибка, то в SQL вставляются только действительные данные.

Я хочу сделать следующее:

  1. вставить всю запись только в том случае, если все данные являются допустимыми, и если произошла одна ошибка при вставке.
  2. удалить все ранее сохраненные данные в SQL.

Итак, я уже пытался с классами TransactionScope и SqlTransaction и даже с классами SQL TRANSACTION, но единственное, что я мог сделать, это вставить допустимые данные, а недопустимые данные были опущены.

Теперь, когда я ищу в Интернете, я обнаружил, что параллельная транзакция невозможна. Кроме того, SQL имеет уровень изоляции, который запрещает параллельные задачи.

Существует ли какой-либо возможный способ выполнить insert в SQL, как ALL or 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;

        }
4 3

4 ответа:

Мне кажется, что вы зацикливаетесь и вызываете метод 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>
}

Ваша проблема заключается в том, что вы открываете и закрываете транзакцию для каждого объекта, который вы записываете в базу данных. Конечно, это означает, что когда вы столкнулись с проблемой с вашими данными, предыдущий правильный уже записан в базу данных и зафиксирован. Поэтому его нельзя откатить назад.

Решение состоит в том, чтобы открыть соединение и транзакцию вне метода вставки данных и передать эти экземпляры объекта внутри метода.

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

Таким образом, метод, который вставленные данные, когда это не удается, вызовут исключение, захваченное вызывающим кодом, который открыл соединение и транзакцию. Затем этот вызывающий код может вызвать метод rollback для отмены всех объектов, вставленных до сих пор.

Проще наложить ограничения на объекты ввода данных, такие как ограничение определенного текстового поля принимать только числа или все, что вы хотите . И как еще одна идея .. попробуйте проверить точность данных перед отправкой запроса в базу данных

Один из способов-создать объект DataTable из списка, сопоставив каждый объект в списке строке объекта DataTable. Затем можно выполнить цикл по строкам DataTable для выполнения операции insert и включить этот блок кода в транзакцию. Это позволит вам предотвратить ввод частичных данных.

Вы можете обратиться к этой ссылке, чтобы получить представление о том, как выполнить преобразование списка в DataTable.