Book Review: Beginning Visual Basic 6 Databases
Let's look at an ADO example of a transaction that uses a few of the optional constants described above for opening our recordset. In this example, we examine the syntax for using a transaction. We just append a "" to each of the more than 8,500 titles in the Titles table. But if there is an error anywhere, the entire enchilada is rolled back and the original table is returned to its fresh as the new-driven snow state (mmm, enchilada's in the snow!). The transaction is sandwiched between the .BeginTrans and .CommitTrans method calls on the connection object:
Dim myConnection As ADODB.Connection Dim myRecordset As ADODB.Recordset Set myConnection = New ADODB.Connection Set myRecordset = New ADODB.Recordset myConnection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=c:BegDBBiblio.mdb" '-Open the connection -- myConnection.Open 'Determine if we conected. If myConnection.State = adStateOpen Then myRecordset.Open "SELECT * FROM TITLES", myConnection, _ adOpenDynamic, adLockOptimistic, adCmdTable Else MsgBox "The connection could not be made." myConnection.Close Exit Sub End If '-just to be sure -- myRecordset.MoveFirst On Error GoTo transError '-here is the top of the transaction sandwich -- myConnection.BeginTrans While Not myRecordset.EOF mcounter = mcounter + 1 myRecordset!Title = myRecordset!Title & "" 'so we don't really change it myRecordset.Update myRecordset.MoveNext Wend '-if we got here ok, then everything is written at once myConnection.CommitTrans myRecordset.Close myConnection.Close Exit Sub transError: myConnection.RollBack myRecordset.Close myConnection.Close MsgBox Err.Description
How It Works
After the connection is established, this example begins a transaction. The data changed in this transaction is either all committed at the end of the transaction, or it is all rolled back to the pre-transaction state. Let's take a look at the important parts of the code.
After we dim and initialize our connection and recordset objects, we build our connection string as we have been doing so far. The connection is then opened by invoking the .Open method of the connection object:
myConnection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.3.51; _ Data Source=c:BegDBBiblio.mdb" '-Open the connection -- myConnection.Open
Then, as a precaution, we check out the .State property of the connection. Recall the general syntax of opening an ADO recordset:
adoRecordset.Open Source, ActiveConnection, CursorType, LockType, Options
Well, in our example, if the connection is open, we pass the .Open method of the recordset an SQL query string as the Source. It is requesting all of the records for the Titles table as the first parameter. Next, we pass in the ActiveConnection connection object, myConnection. Next, we specify the CursorType as an adOpenDynamic cursor for the recordset. This type of cursor permits all movement backwards and forwards, allows the user to make changes to the current record, and even will dynamically update if another user updates any records in the database that are included in our recordset. We will actually see any additions or deletions as they occur by others! We then specify the LockType as adLockOptimistic. This locks record by record and only occurs when the .Update method is called. Since we know this is a table, we pass in the Options parameter as adCmdTable. Now ADO does not have to spend time figuring out what it is going after. We have fully specified the type of recordset we want by passing each parameter.
'Determine if we connected. If myConnection.State = adStateOpen Then myRecordset.Open "SELECT * FROM TITLES", myConnection, _ adOpenDynamic, adLockOptimistic,adCmdTable Else MsgBox "The connection could not be made." myConnection.Close Exit Sub End If
Once our recordset is opened, we want to perform an edit / update on each record in the recordset. Edit / Update is very expensive in terms of processing time. So we sandwich our edit / updates inside a transaction. When the .BeginTrans method is called, everything until the .Commit or .RollBack method of the connection object is reached:
'-here is the top of the transaction sandwich -- myConnection.BeginTrans
Now we do our processing. We loop through the recordset as we usually would. With ADO, no .Edit method is required as it is in DAO. Remember when we discussed ADO being slimmed down by having many redundant methods removed? Well, this is one of them. By simply changing the data, an Edit is assumed. So an Edit still occurs, but it is implicit. We don't have to explicitly call the method. So we modify (edit) each record and then call the .Update method. However, since we are in the transaction sandwich, the results are written to a temporary file. They are not yet committed to the database:
While Not myRecordset.EOF mcounter = mcounter + 1 myRecordset!Title = myRecordset!Title & "" 'so we don't really change it myRecordset.Update myRecordset.MoveNext Wend
If everything goes as planned, when we exit the loop we then call the .CommitTrans method of the connection object. Now, all of the changes are written to the data source at one time. So we are not hitting the disk for each record, writing the changes, then moving to the next record. The changes are kept, where possible, in memory and then blasted all at once to the data source. So instead of doing - say - 700 writes (one for each record), we only do a single bulk write which is much, much faster.
As I said, this technique can be used when you are not 'technically' performing a transaction. In other words, when you are not hitting two tables that require both to be changed simultaneously. When you only need to update a single table as in our simple example, this will be orders of magnitude faster than updating each individual record. Then we close the recordset and the connection when we are finished and exit the sub:
'-if we got here ok, then everything is written at once myConnection.CommitTrans myRecordset.Close myConnection.Close
If we were going to perform the same task on several records such as appending an "" to each title, it would be much faster to just use the .Execute method and use SQL. But we wanted to show an example of the transaction method that you might be able to use in your every day programming.
If we run into a snag, an error is generated. Since we have an active error handler, our VB code jumps to the transError label, which is the start of the error handler. Here we roll back everything that has occurred up to this point. The .RollBack method will bring the system back to the point of the .BeginTrans method. None of the records in the transaction sandwich will be changed. We then close the recordset and connection and display a description of the error from the global Err error object:
transError: myConnection.RollBack myRecordset.Close myConnection.Close MsgBox Err.Description
Using Transactions in Everyday Life
Now this is a cool tip. You can use transactions whenever you have to update several records in a coordinated manner. VB keeps as much in a cache as possible to reduce costly disk writing activity. With a transaction, everything gets written at once, instead of on every .Update method. The speed benefit can be enormous. I use this whenever possible when adding or editing records in a large recordset.
One caveat. If you attempt to do this with the ODBC connection we established earlier, you get the friendly message shown below:
Why? Because the ODBC driver we used does not support transactions. The moral of the story is: know your data source capabilities.
Page 22 of 23