Выполнение нескольких операторов/sqlcomands в рамках одной транзакции ado.net

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

У меня есть приложение winforms, написанное на С# и подключенное к базе данных сервера sql. В некоторых частях моего приложения у меня может быть до 10 команд SQL, работающих с многочисленными таблицами в базе данных, и которые мне нужно содержать в одной транзакции, потому что это ситуация «все или ничего». Если какая-либо из 10 команд не удалась, я хочу, чтобы ни одна из команд не выполнялась.

К осложняющим факторам относятся:

  1. Каждая из 10 sqlcommands имеет несколько параметров
  2. Некоторые из sqlcommands используют INSERT, и мне нужно использовать сгенерированное сервером удостоверение в некоторых из последующих sqlcommands.
  3. Некоторые команды работают с одними и теми же данными, т. е. ранняя команда может ВСТАВИТЬ новый элемент в базу данных, а одна из более поздних команд sql попытается обновить ту же запись.

Любая помощь будет очень признательна, так как я не в своем уме с этой проблемой, потратив на нее большую часть недели, и чувствую, что был достигнут относительно небольшой прогресс.

РЕДАКТИРОВАТЬ:

Команды sql изначально были в разных классах и т. д., однако я подумал, что это может быть проблемой, поэтому я сильно переписал, и теперь ВСЕ команды sql находятся в одном и том же методе, хотя некоторые из команд sql написаны в другом месте и переданы обратно в основной метод.

На данный момент проблема заключается в том, что первая команда (INSERT) выполняется, но транзакция завершается ошибкой при выполнении второй команды, потому что она пытается сослаться на запись базы данных, созданную в первой команде.

Кроме того, я не могу использовать TransactionScope, потому что MSDTC в сети вызывает другие проблемы, поэтому мне нужно справиться с этим с помощью моего приложения и ADO.NET.

РЕДАКТИРОВАТЬ (НЕКОТОРЫЙ КОД)

Вот только первые две команды sql внутри транзакции:

string connectionString = aSystem.ConnectionString;

    using (SqlConnection linkToDB = new SqlConnection(connectionString))
    {
        linkToDB.Open();
        using (SqlTransaction transaction = linkToDB.BeginTransaction())
        {
            try
            {                            
                //...Case...//

                cCase c = new cCase();
                c.CaseType = cboCaseType.Text;
                c.Occupation = txtOccupation.Text;
                c.DateInstructed = txtDateInst.Text;
                c.Status = "Live";
                SqlCommand sqlSaveCase = c.SaveSqlCom();                               
                sqlSaveCase.Connection = linkToDB;
                sqlSaveCase.Transaction = transaction;
                c.SetCaseNo = sqlSaveCase.ExecuteNonQuery().ToString();

                //...IP Link...//

                string strIPID = cboIPAddress.SelectedValue.ToString();
                SqlCommand sqlNewIPLink = cIPID.NewIPLinkSqlCom(strIPID,c.CaseNo,txtIPRef.Text);
                sqlNewIPLink.Connection = linkToDB;
                sqlNewIPLink.Transaction = transaction;
                sqlNewIPLink.ExecuteNonQuery();                //...fails here...//


                //...an additional 8 sql commands follow...//

ПРОБЛЕМА

Команда sqlSaveCase ВСТАВЛЯЕТ новый случай в tblCases, но когда вторая команда sqlNewIPLink пытается использовать идентификатор, созданный первой, она генерирует ошибку внешнего ключа, как будто она не видит вновь созданный случай из первой команды.


person PJW    schedule 28.07.2012    source источник
comment
Эти 10 команд находятся в разных местах кода. то есть разные классы, экземпляры и т.д.?   -  person Marcelo De Zen    schedule 28.07.2012
comment
С чем именно у вас проблемы?   -  person Andreas    schedule 28.07.2012
comment
На самом деле неясно, в чем точно ваши проблемы. Почему нельзя использовать SqlTransaction? Возможно, попробуйте разделить это на несколько вопросов, каждый из которых касается конкретной проблемы, которую можно легко продемонстрировать на простом примере кода.   -  person Nikola Anusev    schedule 28.07.2012
comment
Ваш пример кода действительно работает (хотя, похоже, у вас есть проблема с дизайном). Что именно не работает?   -  person Marcelo De Zen    schedule 28.07.2012
comment
Команда sqlSaveCase ВСТАВЛЯЕТ новый случай в tblCases, но когда вторая команда sqlNewIPLink пытается использовать идентификатор, созданный первой, она генерирует ошибку внешнего ключа, как будто она не видит вновь созданный случай из первой команды.   -  person PJW    schedule 28.07.2012


Ответы (4)


Ошибка была в этой строке

c.SetCaseNo = sqlSaveCase.ExecuteNonQuery().ToString(); 

который должен использовать ExecutreScalar() для получения идентификатора из команды INSERT. Подсказка пришла, когда я в конце концов понял, что он всегда возвращает 1, что, как я полагаю, является, вероятно, просто логическим значением «истина», чтобы сказать, что команда была выполнена. Как только я вместо этого использовал ExecuteScalar в команде, он начал возвращать более значимые идентификаторы, которые распознавали последующие SqlCommands, и, следовательно, никаких проблем с Foregin Key.

person PJW    schedule 28.07.2012
comment
вероятно, просто логическое значение «истина», указывающее, что команда была выполнена. На самом деле это количество строк, на которые повлияла последняя команда. Вставлена ​​1 строка. Менее полезно для INSERT... VALUES(...), когда вы знаете, сколько значений вы отправили, более полезно для UPDATE... WHERE ... - person Ben Voigt; 03.11.2020

Не уверен, что это проблема, но я не вижу, где вы используете c.SetCaseNo во второй SqlCommand.

  //
  // here you are setting c.SetCaseNo
  //
  c.SetCaseNo = sqlSaveCase.ExecuteNonQuery().ToString();

  string strIPID = cboIPAddress.SelectedValue.ToString();

  //
  // but here you're using c.CaseNo
  //
  SqlCommand sqlNewIPLink = cIPID.NewIPLinkSqlCom(strIPID,c.CaseNo,txtIPRef.Text);


  sqlNewIPLink.Connection = linkToDB;
  sqlNewIPLink.Transaction = transaction;
  sqlNewIPLink.ExecuteNonQuery();   
person Marcelo De Zen    schedule 28.07.2012
comment
Спасибо за это - только что опубликовал ответ, который был ExecuteNonQuery вместо ExecuteScalar. SetNewCase был всего лишь вторым модификатором доступа для CaseNo, который обходит проверку, встроенную в мой класс. - person PJW; 28.07.2012

Для этого вы можете использовать класс TransactionScope.

вот хорошее объяснение этого.

надеюсь это поможет.

person Manish Parakhiya    schedule 28.07.2012

в вашем случае рекомендуется использовать шаблон команды. На самом деле, это позволяет вам отменить ваши операции. Транзакция хороша для нескольких операций. Но с 10 операциями лучше выполнять каждую команду в цикле, а в случае сбоя одной команды отменить команду. Это подразумевает, что вы реализуете операцию отката (здесь 10). здесь ссылка с образцом шаблона: http://www.dofactory.com/Patterns/PatternCommand.aspx

person Hassan Boutougha    schedule 28.07.2012