Выполнение нескольких операторов / sqlcomands в одной транзакции ado.net
Я не включаю здесь какой-либо код, потому что я переписывал его так много раз за последние несколько дней, что его действительно не стоит беспокоить, поэтому я объясню свою проблему в общих чертах.
У меня есть приложение winforms, написанное на C# и подключенное к базе данных SQL Server. В некоторых частях моего приложения у меня может быть до 10 SQL-команд, работающих с многочисленными таблицами в базе данных, и которые мне нужно содержать в одной транзакции, потому что это ситуация "все или ничего". Если какая-либо из 10 команд терпит неудачу, я хочу, чтобы ни одна из команд не была зафиксирована.
Осложняющие факторы включают в себя:
- Каждая из 10 команд sql имеет несколько параметров
- Некоторые из команд sql используют INSERT, и мне нужно использовать сгенерированный сервером идентификатор в некоторых последующих командах sql.
- Некоторые из команд работают с одними и теми же данными, т. Е. Более ранняя команда может ВСТАВИТЬ новый элемент в базу данных, а одна из более поздних команд 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 пытается использовать идентификатор, созданный первым, она генерирует ошибку внешнего ключа, как будто она не видит вновь созданный случай из первой команды.
4 ответа
Ошибка была в этой строке
c.SetCaseNo = sqlSaveCase.ExecuteNonQuery().ToString();
который должен использовать ExecutreScalar() для получения идентификатора из команды INSERT. Подсказка появилась, когда я в конце концов понял, что всегда возвращал 1, что, я полагаю, является просто логическим значением "true", чтобы сказать, что команда была выполнена. Как только я использовал ExecuteScalar в команде, он начал возвращать более значимые идентификаторы, которые распознавались последующими SqlCommands, и, следовательно, никаких проблем с ключом Foregin.
Не уверен, что это проблема, но я не вижу, где вы используете 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();
В вашем случае рекомендуется использовать шаблон команд. На самом деле, это позволяет вам отменить ваши операции. Транзакция хороша для нескольких операций. Но с 10 операциями лучше выполнять каждую команду в цикле и, если одна команда не удалась, отменить команду. Это подразумевает, что вы реализуете операцию отката (здесь 10). здесь ссылка с образцом шаблона:http://www.dofactory.com/Patterns/PatternCommand.aspx
Вы можете использовать класс TransactionScope для него.
Вот хорошее объяснение этого.
надеюсь это поможет.