Updating Data Sources with Transactions


One of the features of most database systems, and some other types of data store, is the ability to use transactions . Simply put, a transaction is a series of events that are all completed, or of which none are completed “there is never an intermediate result where some but not all of the events within the transaction occur.

The name transaction comes from real-world scenarios such as purchasing an item in a store where you give the seller money in exchange for goods. Unless one of you gets cheated, the transaction will either succeed with both parties happy at the outcome (you pay your money and get your goods), or fail where neither action occurs. There should never be an outcome where you pay money and don't get the goods, or where you get goods but don't pay the money.

In this section, you'll look at two types of transactions:

  • Database transactions , where database-specific statements control the transaction and it is carried out within the database itself. Usually the stored procedure within the database contains the transaction statements.

  • Connection-based transactions , where the statements that control the transaction, and the execution and management of that transaction are outside the database. Usually these are a feature of the Connection object that executes a SQL statement or stored procedure.

    Note

    While it is possible to write stored procedures that perform transactions across different databases on the same server, this is outside the scope of this chapter. It is also possible to use the services of another application, such as Windows 2000 Component Services (or MTS in Windows NT4) to perform a distributed transaction, where a series of events spread across different databases and applications on different servers are managed as a single transaction. Chapter 17 looks briefly at this topic.

Database Transactions

In a database system such as SQL Server, you can specify transaction operations within a stored procedure using vendor-specific statements like BEGIN TRANSACTION to start a new transaction, COMMIT TRANSACTION to accept all the updates and permanently commit the changes to the data, and ROLLBACK TRANSACTION to cancel all the changes made within the current transaction.

We've provided an example page that uses a transacted stored procedure. The stored procedure, named DoBookArchive , is created within the WroxBooks database by the SQL script provided with the samples.

The DoBookArchive Stored Procedure

The DoBookArchive stored procedure moves a row from the BookList table into another table named ArchiveBooks , within the same database. If the process succeeds, the transaction is committed and the updates are permanently applied to the database tables. If there is an error when writing to the ArchiveBooks table, or when deleting the book from the BookList table, both actions are rolled back and the tables are left in exactly the same state as before “neither is affected by the procedure.

However, to make it repeatable while you are experimenting with the example, the stored procedure always starts by deleting any existing book with the same ISBN (the primary key) in the ArchiveBooks table. This action will also be rolled back if the complete transaction fails, so if a book has been archived (and hence deleted from the BookList table) it will not be deleted from the ArchiveBooks table if you run the stored procedure again with the same ISBN. In this case, the INSERT statement will fail because the book is not in the BookList table, and so the entire transaction is rolled back undoing the DELETE operation on the ArchiveBooks table. The code for the stored procedure follows :

  CREATE PROCEDURE DoBookArchive   @ISBN varchar(12), @Result integer output AS   DECLARE @verror int   BEGIN TRANSACTION   DELETE FROM ArchiveBooks WHERE ISBN=@ISBN   INSERT INTO ArchiveBooks (ISBN, Title, PublicationDate)   SELECT * FROM BookList WHERE ISBN LIKE @ISBN   SELECT @verror = @@ERROR, @Result = @@ROWCOUNT   IF @verror <> 0 GOTO on_error   IF @Result > 0   BEGIN   DELETE FROM BookList WHERE ISBN=@ISBN   IF @@ERROR <> 0 GOTO on_error   COMMIT TRANSACTION   END   ELSE   ROLLBACK TRANSACTION   RETURN   on_error:   SELECT @Result = -1   ROLLBACK TRANSACTION   RETURN  

The Transacted Stored Procedure Example

The example page Updating Data with a Transacted Stored Procedure ( transacted-storedproc.aspx ) uses the stored procedure just described. We've arranged for it to use the same ISBN code as the previous example that inserts and deletes a book in the BookList table, so that you can see the results of this example by running it after inserting the new book and after deleting it. As shown in Figure 10-5, the stored procedure in this example will succeed providing that you have run the previous example to insert the new book row:

click to expand
Figure 10-5:

If you then run the page again, as in Figure 10-6, it will show that the stored procedure failed to find the book in the BookList table (because, of course, it's just been moved to the ArchiveBooks table):

click to expand
Figure 10-6:

The Code for the Transacted Stored Procedure Example

As in the earlier examples, let's start by specifying the name of the stored procedure and displaying it in the page, and then create the Connection and Command objects you'll need to execute it. Also set the CommandType of the Command object to indicate that you'll be executing a stored procedure. Then you can create the parameters for the command. This time there are only two “an input parameter to hold the ISBN of the book you want to archive and an output parameter to hold the result.

  'specify the stored procedure name   Dim strSQL As String = "DoBookArchive"   outSQL.InnerText = strSQL 'and display it     'create connection and command   Dim objConnect As New OleDbConnection(strConnect)   Dim objCommand As New OleDbCommand(strSQL, objConnect)   objCommand.CommandType = CommandType.StoredProcedure     'create an input Parameter object named 'ISBN' with the correct data   'type to match a SQL database 'varchar' field of 12 characters   Dim objParam As OleDbParameter   objParam = objCommand.Parameters.Add("ISBN", OleDbType.VarChar, 12)   objParam.Direction = ParameterDirection.Input   objParam.Value = "199999999"     'create an output Parameter object named 'Result' with the correct   'data type to match a SQL database 'integer' field   'specify that it's an Output parameter so no value required   objParam = objCommand.Parameters.Add("Result", OleDbType.Integer)   objParam.Direction = ParameterDirection.Output     'display the value of the input parameter   outInParams.InnerText = "ISBN='" & objCommand.Parameters("ISBN").Value & "'"  

Executing the Stored Procedure and Displaying the Results

The next step is to open your connection and execute the stored procedure. Then you can collect the result from the output parameter and display it, along with some accompanying explanatory text.

  Try   'execute the stored procedure   objConnect.Open()   objCommand.ExecuteNonQuery()   objConnect.Close()   Catch objError As Exception   outError.InnerHtml = "* Error while updating original data.<br />" _   & objError.Message & "<br />" & objError.Source   Exit Sub 'stop execution   End Try   'collect and display the value of the output parameter   Dim intResult As Integer = objCommand.Parameters("Result").Value   Dim strResult As String = "Result='" & CStr(intResult) & "'<br />"   Select Case intResult   Case -1: strResult &= "Error occurred while attempting archive"   Case 0: strResult &= "Failed to archive book  no matching book found"   Case > 0: strResult &= "Successfully archived the specified book"   End Select   outOutParams.InnerHtml = strResult  

Notice that you didn't have to do anything extra to benefit from the transaction within the stored procedure “you just executed it and checked the result to see what actually happened . This is not the case, however, when you use the other type of transaction, a connection-based transaction. You'll see how different working with this type of transaction is next.

Connection-Based Transactions

The previous example shows how you can use a transaction within a stored procedure (a database transaction) to ensure that operations on your data either all succeed or are all rolled back. A second way of using a transaction is through the capabilities of the Connection object. All Connection objects ( SqlConnection, OelDbConnection , OdbcConnection , and OracleConnection) can be used to perform transacted data updates.

While the way you actually apply a transaction is different from the stored-procedure transaction used in the previous example, the terminology is broadly the same. The three methods of the Connection class concerned with managing transactions are shown in the following table.

Connection.BeginTransaction

Starts a new transaction on this connection and all subsequent changes to the data become part of the transaction until it is committed or rolled back.

Transaction.Commit

Commits all changes made to the data within this transaction since it was started. The changes are made permanent in the target data store.

Transaction.Rollback

Abandons all changes made to the data within this transaction since it was started. The changes are removed from the target data store.

The Transaction Class

In ADO.NET, there are separate classes that implement transactions, one for each of the different types of Connection . To start a transaction, call the BeginTransaction method of the current Connection object. This returns a Transaction object that you must then assign to any Command objects that you want to enroll into that transaction.

To end a transaction and commit all the changes to the database, call the Commit method of the Transaction object (note that it's not a method of the Connection object as you might at first have expected). To abandon all changes to the data, you can call the Transaction object's Rollback method instead.

Notice also that you have to manually enroll any Command objects into the transaction. While this might seem odd, it does allow you to have multiple transactions in progress, and use whichever is appropriate for each command you carry out on the database. You can also create a nested transaction (that is a transaction that executes within another transaction) by creating a new Transaction object and calling the Begin method.

A Connection-Based Transaction Example

To see the transaction methods in action, open the example Transactional Data Updates with a Command Object ( update - with - transaction.aspx ) shown in Figure 10-7. This page creates three SQL statements that are used to update the titles of three books in the BookList table to reflect the current date and time, and then it executes these statements. Afterwards, it reads the rows back from the database and displays the details to confirm that the updates were successful.

click to expand
Figure 10-7:

You can see in Figure 10-7 that the transaction was committed, and the three rows were updated. However, this is only because the page contains logic that uses the current time in seconds to decide whether to commit or roll back the transaction. While not a real-world scenario, it is done so that you can see the result of rolling back a transaction as well as committing it. After running the page again where the time has an even number of seconds, as shown in Figure 10-8, the transaction is rolled back and so the titles are not updated.

click to expand
Figure 10-8:

The Code for the Connection-Based Transaction Example

The only real differences in the way that this page works, when compared to the other examples that use SQL statements to update the data source, is that you have to call the transaction methods at the appropriate times “effectively managing the transaction yourself. Instead of a stored procedure within the database itself deciding whether to commit or rollback the changes (usually dependent on the outcome of one of the statements in the stored procedure), you decide within your ASP code if the transaction should be committed or rolled back.

As usual, start by creating the SQL statements you'll be executing against the BookList table to update the book titles, as shown in the following code. Then you can create your Connection and Command objects, and declare a variable to hold the number of rows affected by your updates. The initial value is set to zero here, though this is not actually required (zero is the default value), but it helps to illustrate how the code works, and ensures that you can safely add on the result each time you execute a SQL statement.

  'specify the SQL statements to update the data   Dim strNow, strSQL1, strSQL2, strSQL3 As String   Dim datNow As DateTime = Now()   strNow = datNow.ToString("dd-M-yy \a\t hh:mm:ss")   strSQL1 = "UPDATE BookList SET Title = 'Book One Written on " _   & strNow & "' WHERE ISBN='1100000001'"   outSQL1.InnerText = strSQL1 'and display it   strSQL2 = "UPDATE BookList SET Title = 'Book Two Written on " _   & strNow & "' WHERE ISBN='1100000002'"   outSQL2.InnerText = strSQL2 'and display it   strSQL3 = "UPDATE BookList SET Title = 'Book Three Written on " _   & strNow & "' WHERE ISBN='1100000003'"   outSQL3.InnerText = strSQL3 'and display it     'create connection and command and variable to hold result   Dim objConnect As New OleDbConnection(strConnect)   Dim objCommand As New OleDbCommand()   Dim intRowsAffected As Integer = 0  
Starting a Transaction

You need a variable to hold the Transaction object that will be returned when we start a transaction, and so declare this next, as shown in the following code. Then open your connection, and execute the BeginTransaction method to start a new connection-based transaction. You can assign the Transaction object that is returned to your objTransaction variable.

Now you are ready to execute your three SQL UPDATE statements using the Command object you created earlier on. You created it without providing any values for the constructor parameters, so you have to assign your Connection object to its Connection property. Also set the CommandType to indicate that you're using a SQL statement (though this is the default if not specified). Once your Command object is set up, you also have to enroll it into the current transaction.

Notice that you can only do so after you've set the Connection property, and if you want to change the Connection property afterwards you first have to un-enrol it by setting the Transaction property of the Command object to Nothing .

  'declare a variable to hold a Transaction object   Dim objTransaction As OleDbTransaction     Try     'open connection before starting transaction   objConnect.Open()     'start a transaction for this connection   objTransaction = objConnect.BeginTransaction()     'specify the Connection object and command type for the Command   objCommand.Connection = objConnect   objCommand.CommandType = CommandType.Text     'attach the current transaction to the Command object   'must be done after setting Connection property   objCommand.Transaction = objTransaction  
Executing the Commands and Committing or Rolling Back

The next step is to assign each SQL statement to the CommandText property in turn and execute it. Then the next place where you need to consider how to handle the transaction that you've started is if an error occurs while executing the SQL statements. In an error situation, you would usually call the Rollback method of the Transaction object to cancel any changes that have been applied to the source data as shown.

If there is no error, and all three SQL statements have successfully executed, you would normally call the Commit method of the Transaction object to permanently apply the changes to the data store. However, in this example, check the number of seconds in the current time and only call Commit if this is an odd number. If it's an even number, you can call the Rollback method to abandon all updates.

  'specify the select statement to use for the first update   objCommand.CommandText = strSQL1     'execute the SQL statement against the command to fill the DataReader   'keep track of number of records originally updated   intRowsAffected += objCommand.ExecuteNonQuery()     'repeat using the select statement for the second update   objCommand.CommandText = strSQL2   intRowsAffected += objCommand.ExecuteNonQuery()     'repeat using the select statement for the third update   objCommand.CommandText = strSQL3   intRowsAffected += objCommand.ExecuteNonQuery()     Catch objError As Exception     'error encountered so roll back all the updates   objTransaction.Rollback()   'display error details   outError.InnerHtml = "* Error while updating original data.<br />" _   & objError.Message & "<br />" & objError.Source   Exit Sub ' and stop execution     End Try     'all seems OK so can now commit all the updates. However as an   'illustration of the technique only do so if the current time   'has an odd number of seconds. If not, rollback all changes   Dim strCommit As String   If Second(datNow) Mod 2 = 0   objTransaction.Rollback()   strCommit = "rolled back"   Else   objTransaction.Commit()   strCommit = "committed"   End If  

Afterwards you can read the values of the rows using a DataReader object and display them in the page. This is identical to the way you did it in the first example in this chapter, so the code is not repeated here.

Having looked briefly at how you can use transactions to ensure multiple data updates all succeed, or all fail, we'll move on to a different topic. The DataSet object introduced in previous chapters has the facility to automatically update the source data from which it was created “or in fact any data store for which the structure and contents of the tables within the DataSet are of the appropriate format. This is the focus of the next section.




Professional ASP. NET 1.1
Professional ASP.NET MVC 1.0 (Wrox Programmer to Programmer)
ISBN: 0470384611
EAN: 2147483647
Year: 2006
Pages: 243

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net