Опубликован: 07.05.2010 | Доступ: свободный | Студентов: 1678 / 62 | Оценка: 4.56 / 4.06 | Длительность: 34:11:00
Лекция 11:

Основы ADO.NET

Транзакции

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

Поставщики данных включают поддержку транзакций, которые начинаются при вызове метода Connection.BeginTransaction(). Принимается транзакция методом Transaction.Commit(), а отменяется, если возникло исключение, методом Transaction.Rollback().

Приведем пример, в котором в таблицу Employees БД Nordhwind добавляется две записи через механизм контроля транзакций. Эти записи можно было бы добавить и обычным способом, но в критических случаях делать это нужно под контролем транзакций.

  • Сделайте из страницы TestStoredProcedure.aspx копию с именем TestTransaction.aspx и определите ее стартовой
  • Заполните страницу следующим кодом
    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        // Объявляем переменные-ссылки как члены класса страницы
        string sql1 = "INSERT INTO Employees (LastName, FirstName, HireDate) "
            + "VALUES ('Зиборов', 'Алексей', GETDATE())";
        string sql2 = "INSERT INTO Employees (LastName, FirstName, HireDate) "
            + "VALUES ('Погодаева', 'Татьяна', GETDATE())";
        System.Data.SqlClient.SqlConnection con;
        System.Data.SqlClient.SqlCommand cmd1, cmd2;
        System.Data.SqlClient.SqlTransaction tran = null;
        
        /**** Для отладки ****/
        string sql3 = "DELETE FROM Employees WHERE EmployeeID > 9";
        System.Data.SqlClient.SqlCommand cmd3;
        /*********************/
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Извлечь строку соединения из web.config
            string connectString = System.Web.Configuration.WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
        
            // Создать объект соединения
            con = new System.Data.SqlClient.SqlConnection(connectString);
            
            // Создать объекты команд
            cmd1 = new System.Data.SqlClient.SqlCommand(sql1, con);
            cmd2 = new System.Data.SqlClient.SqlCommand(sql2, con);
            
            /**** Для отладки ****/
            cmd3 = new System.Data.SqlClient.SqlCommand(sql3, con);
            /*********************/
        }
        
        protected void AddEmployee_Click(object sender, EventArgs e)
        {
            try // Попытка
            {
                // Открыть соединение
                con.Open();
                
                /**** Для отладки ****/
                cmd3.ExecuteNonQuery(); // Удалить последние записи
                /*********************/
                
                // Начать транзакцию
                tran = con.BeginTransaction();
        
                // Пометить команды включенными в транзакцию
                cmd1.Transaction = tran;
                cmd2.Transaction = tran;
        
                // Выполнить обе команды
                // Должны выполняться только команды транзакции!!!
                cmd1.ExecuteNonQuery();
                cmd2.ExecuteNonQuery();
         
                // Искусственная генерация исключения
                // для тестирования отмены транзакции
                //throw new ApplicationException();
       
                // Подтвердить и закончить транзакцию
                tran.Commit();
                
                // Посчитать количество записей таблицы
                // Теперь можно выполнять другие команды
                cmd1.CommandText = "SELECT COUNT(*) FROM Employees";
                string recCount = cmd1.ExecuteScalar().ToString();
                
                // Выдать результат
                lblInfo.Text = "Добавлены две записи!<br />";
                lblInfo.Text += "Общее число записей " + recCount;
            }
            catch // Откат при любом исключении
            {
                lblInfo.Text = "Транзакция отменена!";
                tran.Rollback();
            }
            finally // Обязательное завершение
            {
                // Закрыть соединение по-любому
                con.Close();
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:Label ID="lblInfo" runat="server" />
            <br />
            <br />
            <asp:Button ID="AddEmployee" runat="server" 
                Text="Выполнить транзакцию"
                OnClick="AddEmployee_Click" />
            <asp:LinkButton ID="btnResult" runat="server" 
                PostBackUrl="~/TestDataReader.aspx">
                Показать список</asp:LinkButton>
        </form>
    </body>
    </html>
  • Исполните страницу и получите следующий отклик при выполнении транзакции

  • Просмотрите таблицу Employees БД Nordhwind по гиперссылке, результат выполнения транзакции будет примерно таким
  • Служащий Ms. Davolio, Nancy - работает с 01.05.1992
  • Служащий Dr. Fuller, Andrew - работает с 14.08.1992
  • Служащий Ms. Leverling, Janet - работает с 01.04.1992
  • Служащий Mrs. Peacock, Margaret - работает с 03.05.1993
  • Служащий Mr. Buchanan, Steven - работает с 17.10.1993
  • Служащий Mr. Suyama, Michael - работает с 17.10.1993
  • Служащий Mr. King, Robert - работает с 02.01.1994
  • Служащий Ms. Callahan, Laura - работает с 05.03.1994
  • Служащий Ms. Dodsworth, Anne - работает с 15.11.1994
  • Служащий Зиборов, Алексей - работает с 25.04.2007
  • Служащий Погодаева, Татьяна - работает с 25.04.2007

Обратите внимание, что пока транзакция находится в процессе работы, должны выполняться только команды транзакции. Область работы транзакции определяется операторными скобками con.BeginTransaction(); ... tran.Commit() ;

Чтобы протестировать свойство отката (отмены) транзакции, можно вставить непосредственно перед вызовом метода tran.Commit() строку генерации исключения

throw new ApplicationException();

Точки сохранения для отката транзакции

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

Немного изменим предыдущий пример, введя возможность частичного отката.

  • Скопируйте страницу TestTransaction.aspx в TestTransactionSave.aspx и назначьте ее стартовой
  • Измените код новой страницы, чтобы он был таким
    <%@ Page Language="C#" %>
        
    <script runat="server">
        
        // Объявляем переменные-ссылки как члены класса страницы
        string sql1 = "INSERT INTO Employees (LastName, FirstName, HireDate) "
            + "VALUES ('Зиборов', 'Алексей', GETDATE())";
        string sql2 = "INSERT INTO Employees (LastName, FirstName, HireDate) "
            + "VALUES ('Погодаева', 'Татьяна', GETDATE())";
        System.Data.SqlClient.SqlConnection con;
        System.Data.SqlClient.SqlCommand cmd1, cmd2;
        System.Data.SqlClient.SqlTransaction tran = null;
        
        /**** Для отладки ****/
        string sql3 = "DELETE FROM Employees WHERE EmployeeID > 9";
        System.Data.SqlClient.SqlCommand cmd3;
        /*********************/
        
        protected void Page_Load(object sender, EventArgs e)
        {
            // Извлечь строку соединения из web.config
            string connectString = System.Web.Configuration.WebConfigurationManager.
                ConnectionStrings["Northwind"].ConnectionString;
        
            // Создать объект соединения
            con = new System.Data.SqlClient.SqlConnection(connectString);
            
            // Создать объекты команд
            cmd1 = new System.Data.SqlClient.SqlCommand(sql1, con);
            cmd2 = new System.Data.SqlClient.SqlCommand(sql2, con);
            
            /**** Для отладки ****/
            cmd3 = new System.Data.SqlClient.SqlCommand(sql3, con);
            /*********************/
        }
        
        protected void AddEmployee_Click(object sender, EventArgs e)
        {
            try // Попытка
            {
                // Открыть соединение
                con.Open();
                
                /**** Для отладки ****/
                cmd3.ExecuteNonQuery(); // Удалить последние записи
                /*********************/
                
                // Начать транзакцию
                tran = con.BeginTransaction();
        
                // Пометить команды включенными в транзакцию
                cmd1.Transaction = tran;
                cmd2.Transaction = tran;
        
                // Выполнить первую команду транзакции
                cmd1.ExecuteNonQuery();          
                // Пометить точку отката
                tran.Save("Ziborov");
        
                // Выполнить вторую команду транзакции
                cmd2.ExecuteNonQuery();   
                // Выбросить исключение
                throw new ApplicationException();
            }
            catch // Откат
            {
                // Откатить частично
                tran.Rollback("Ziborov");
                
                // Подтвердить и закончить транзакцию
                tran.Commit();
                
                lblInfo.Text = "Транзакция частично отменена!";
        
                // Посчитать количество записей таблицы
                cmd1.CommandText = "SELECT COUNT(*) FROM Employees";
                string recCount = cmd1.ExecuteScalar().ToString();
        
                // Выдать результат
                lblInfo.Text = "Добавлена одна запись!<br />";
                lblInfo.Text += "Общее число записей " + recCount;
            }
            finally // Обязательное завершение
            {
                // Закрыть соединение по-любому
                con.Close();
            }
        }
    </script>
        
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head id="Head1" runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <asp:Label ID="lblInfo" runat="server" />
            <br />
            <br />
            <asp:Button ID="AddEmployee" runat="server" 
                Text="Выполнить транзакцию"
                OnClick="AddEmployee_Click" />
            <asp:LinkButton ID="btnResult" runat="server" 
                PostBackUrl="~/TestDataReader.aspx">
                Показать список</asp:LinkButton>
        </form>
    </body>
    </html>
  • Выполните страницу и убедитесь, что откат выполняется только до точки сохранения, а принимается только результат выполнения предыдущей команды (или блока команд) транзакции