В прошлой статье мы познакомились с таким понятием, как транзакция. Было рассмотрено, каким образом можно управлять транзакциями на уровне SQL Server. Сегодня мы научимся создавать и использовать транзакции из программного кода C#.
В .NET существуют два типа транзакций: локальная транзакция и распределенная транзакция. Локальные транзакции работают в пределах одного соединения к базе данных, а распределенные способны отслеживать команды к нескольким источникам данных одновременно.
Сначала рассмотрим классический пример локальной транзакции на языке C#. В рамках локальной транзакции объявляется несколько команд, внутри одного соединения. После выполнения команд принимается решение, была ли транзакция успешной? Если да – данные фиксируются в базе, в противном случае – транзакция откатывается. Такой подход похож на хранимую процедуру, где, как известно, могут выполняться несколько команд в одной транзакции.
1: using (SqlConnection sqlConnection = new SqlConnection("ConnectionString"))
2: {
3: SqlTransaction transaction =
4: sqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);
5:
6: SqlCommand command1 = new SqlCommand();
7: command1.CommandText = "INSERT INTO A VALUES (1, 'abc');";
8: command1.Connection = sqlConnection;
9: command1.Transaction = transaction;
10:
11: SqlCommand command2 = new SqlCommand();
12: command2.CommandText = "INSERT INTO B VALUES (1, 2, 3);";
13: command2.Connection = sqlConnection;
14: command2.Transaction = transaction;
15:
16: try
17: {
18: sqlConnection.Open();
19: command1.ExecuteNonQuery();
20: command2.ExecuteNonQuery();
21:
22: transaction.Commit();
23: }
24: catch
25: {
26: transaction.Rollback();
27: }
28: }
В данном примере открывается соединение к базе данных, из которого мы получаем объект транзакции. Команда sqlConnection.BeginTransaction транслируется в команду SQL Server (BEGIN TRANSACTION). Параметр команды принимает уровень изоляции транзакции (полное описание изоляционных уровней смотри в предыдущей статье).
Далее мы создаем две команды с операциями вставки и устанавливаем им текущую транзакцию. После этого в блоке try-catch мы пытаемся выполнить обе команды и зафиксировать транзакцию. Если в процессе выполнения одной из команд возникнет исключение - обе команды будут отменены.
Напомню, что локальная транзакция может работать только в пределах одного соединения с базой данных. Если попробовать открыть несколько соединений и присвоить их командам одну транзакцию – будет сгенерировано исключение.
Для реализации задачи мониторинга нескольких соединений в .NET существуют распределенные транзакции. Рассмотрим пример такой транзакции:
1: using (TransactionScope distibutedTransaction = new TransactionScope())
2: {
3: using (SqlConnection sqlConnection1 = new SqlConnection("ConnectionString1"))
4: {
5: SqlCommand command1 = new SqlCommand();
6: command1.CommandText = "INSERT INTO A VALUES (1, 'abc');";
7: command1.Connection = sqlConnection1;
8:
9: command1.Connection.Open();
10: command1.ExecuteNonQuery();
11: }
12:
13: using (SqlConnection sqlConnection2 = new SqlConnection("ConnectionString2"))
14: {
15: SqlCommand command2 = new SqlCommand();
16: command2.CommandText = "INSERT INTO B VALUES (1, 2, 3);";
17: command2.Connection = sqlConnection2;
18:
19: command2.Connection.Open();
20: command2.ExecuteNonQuery();
21: }
22:
23: distibutedTransaction.Complete();
24: }
Как вы можете заметить, синтаксис использования распределенной транзакции немного отличается от кода локальной транзакции.
Сначала создается объект распределенной транзакции. В данном случае уже нет необходимости явно назначать каждой команде транзакцию. Объект TransactionScope будет отслеживать все соединения и команды, объявленные в коде программы до вызова метода Complete(). При возникновении исключения в любой команде вся распределенная транзакция будет отменена автоматически.
Распределенная транзакция доступна для всех провайдеров, которые реализуют интерфейс ITransaction.
Метод Complete() объекта TransactionScope говорит системе, что все операции в рамках данной распределенной транзакции завершены, однако это метод не фиксирует команды транзакции! Фиксация происходит при ликвидации объекта TransactionScope.
Если убрать команду using в коде выше, то данные транзакции будут зафиксированы только при обработке объекта TransactionScope сборщиком мусора, и до этого момента соединения с базами данных будут оставаться открытыми. Такая ситуация может крайне негативно отразится на производительности приложения. Поэтому рекомендуется использовать объект TransactionScope в директиве using, либо явно вызывать у него метод Dispose().
Если же не вызвать метод Complete() вовсе, то система будет думать, что транзакция выполнилась с ошибками и в любом случае будет выполнен ее откат.
В примере выше, распределенная транзакция используется для контроля двух команд к разным базам данных Microsoft SQL Server. Однако, провайдер доступа к базе данных не имеет значения. Вы можете использовать распределенную транзакцию для команд к базам данных Oracle, MySQl и т.д.
Рассмотрим пример распределенной транзакции при участии соединений с разными система управления базами данных:
1: using (TransactionScope distibutedTransaction = new TransactionScope())
2: {
3: using (SqlConnection MSSqlConnection = new SqlConnection("ConnectionString1"))
4: {
5: SqlCommand command = new SqlCommand();
6: command.CommandText = "INSERT INTO A VALUES (1, 'abc');";
7: command.Connection = MSSqlConnection;
8:
9: command.Connection.Open();
10: command.ExecuteNonQuery();
11: }
12:
13: using (OracleConnection oracleConnection = new OracleConnection("ConnectionString2"))
14: {
15: OracleCommand command = new OracleCommand();
16: command.CommandText = "INSERT INTO B VALUES (1, 2, 3);";
17: command.Connection = oracleConnection;
18:
19: command.Connection.Open();
20: command.ExecuteNonQuery();
21: }
22:
23: distibutedTransaction.Complete();
24: }
В данном примере внутри транзакции находятся команды к базам данным Microsoft SQL и Oracle. (код исключительно демонстрационный – такое соединение с Oracle больше не поддерживается в .NET Framework – необходимо использовать сторонние провайдеры)
В данной статье мы познакомились со средствами управления транзакциями баз данных из программного кода .NET приложения на языке C#.
Евгений, всё конечно хорошо, но первый же твой листинг выкинет исключение InvalidOperationException при вызове функции BeginTransactioin(). Так как у тебя подключение закрыто. Ты листинги хоть проверяй перед публикацией.
ОтветитьУдалить