Transactions are groups of database commands that execute as a package. When dealing with real-world applications, you may need to ensure that multiple actions within the application either succeed in their entirety, or fail in their entirety. In other words, all actions should succeed, or none of them should. Transactions help you to accomplish this. You begin a transaction, and then, if all actions are successful, you commit it. Committing a transaction causes the actions to be saved to the data source. Otherwise, you roll it back, which puts the database back to its state was prior to the transaction beginning. As so often, there's an acronym for the concepts that make up transaction processing. The word is ACID, and it works like this:
Transactions can be handled in three places within .NET database applications.
Transactions do affect performance - they keep resources locked until the transaction is committed or rolled back. Because of this, other applications will have to wait if they need to access these resources. You should therefore be prudent when deciding whether you need transactions. Also, you should start transactions as late as possible within your code, and then commit them or roll them back as soon as possible, to make the lock time as short as possible.
Transactions in SQLAs stated above, transactions in SQL are handled through the use of the BEGIN TRANSACTION, COMMIT TRANSACTION, and ROLLBACK TRANSACTION statements, whose names are self-explanatory. If you wish, you can shorten them to use the keyword TRANS instead of TRANSACTION.
Any statements executed after a COMMIT TRANSACTION or a ROLLBACK TRANSACTION are no longer a part of a transaction, unless a new transaction is begun. Try It Out - Transactions in SQL
Let's start putting transactions to use. A good example of a situation where you need transactions within a stored procedure is when you're auditing the various data changes taking place within your database. Auditing is important when you need accountability for the changes that are made to an application. If you have multiple users maintaining employee records, for example, and someone creates an employee with erroneous data, you need to be able to look at an audit entry and find out exactly who performed the operation, so that the erroneous data can be corrected.
How It WorksAll of the interest here lies in the revisions to the stored procedure. The first change happens right at the start, where we begin the transaction.
BEGIN TRANSACTION Next, as usual, we attempt to insert the new employee record, and then check to see whether that attempt was successful:
INSERT INTO employees (firstname, lastname) VALUES (@FirstName, @LastName) IF @@ERROR = 0 If @@ERROR = 0, we know that the attempt was successful, and we can insert an entry into the audit log.
BEGIN INSERT INTO tblAuditLog (Action, Msg) VALUES ('New employee created', 'Employee''s name is ' + @FirstName + ' ' + @LastName) But we now have to check to see if the audit log entry itself was successful.
IF @@ERROR = 0 BEGIN And if it was, then we commit the transaction.
COMMIT TRANSACTION END However, if the audit log entry wasn't successfully created, we roll back the transaction. This will cause the employee record that we created above to be removed. We don't want a new employee added without its associated audit log entry.
ELSE BEGIN ROLLBACK TRANSACTION We also raise a custom error message, and create an entry in the error log. It's important to note that since the transaction has been rolled back, any commands following the rollback are executed outside of the transaction scope.
RAISERROR('Audit of employee creation failed.', 1, 1) INSERT INTO tblErrorLog (ErrType, Msg) VALUES ('Audit Failure', 'Audit of employee creation failed.') As seen in previous examples, if an error occurrs while creating the entry in the error log, we raise another error.
IF @@ERROR <> 0 BEGIN RAISERROR('Error log couldn''t be updated.', 1, 1) END END END If the attempt to insert a new employee wasn't successful, we come to this code block:
ELSE BEGIN The first thing we do after an unsuccessful attempt to insert a new employee is to roll back the transaction.
ROLLBACK TRANSACTION Then, finally, we raise our error and create an entry in the audit log.
RAISERROR('Employee duplicate record couldn''t be created.', 1, 1) INSERT INTO tblErrorLog (ErrType, Msg) VALUES ('Duplicate record', 'Employee duplicate record couldn''t be created.') IF @@ERROR <> 0 BEGIN RAISERROR('Error log couldn''t be updated.', 1, 1) END END To complete this example, let's create a situation where the audit log entry can't be created. As we've done before, we'll change one line in the stored procedure so that a null value is specified for the Action field, which doesn't allow nulls. Here's the changed line.
INSERT INTO tblAuditLog (Action, Msg) VALUES (NULL, 'Employee''s name is ' + @FirstName + ' ' + @LastName) Now when we try to insert a new employee - say, Sharon Pipes - we get an error that says we can't insert our record into the tblAuditLog table. And if we look at the tblErrorLog table, that's exactly what we find:
The important thing to look out for here, however, is that even though the original insert into the Employees table succeeded, it was rolled back because no entry could be placed into tblAuditLog. Take a look at the Employees table, and you'll find that no new entry exists. Moreover, as we mentioned in the walkthrough of the stored procedure, the entry in tblErrorLog was successful because the transaction was over by the time the insert into that table took place.
Transactions in ADO.NETTransactions in ADO.NET work the same way as they do in SQL: you begin a transaction and then, depending on the circumstances, you either commit that transaction or roll it back. When you need a transaction in your ADO.NET code, the connection object is used to provide a transaction object. This object is then used with each command object that you want to include in the transaction. The diagram below shows the relationship between the three object types.
Here's how you use transactions within your ASP.NET applications:
Try It Out - Transactions in ADO.NET
Let's create a new example that uses the Employees table and the tblAuditLog table we created earlier. We're going to create a new employee named Brian Berry.
The first time you run this, you'll see the following messages:
Audit Log entry added Employee added However, the second time it's run, the employee insert will fail because an employee called Brian Berry already exists. Therefore, you'll see the error message:
Audit Log entry added There was an error When you look at the database, however, you'll only see one audit log entry:
As you can see, when we ran it the second time, the successful audit entry that was inserted was rolled back, leaving only the first audit log entry that was created during the first run.
How It WorksWhat are the important parts of this code? It starts right after we open the connection to the database, when we create a SqlTransaction object through the use of the connection's BeginTransaction() method:
conn.Open() Tran = conn.BeginTransaction() We then create the (first) command object. On this occasion, we've created a variable to hold the SQL query, and passed this variable to the command's constructor, along with the connection object.
Try sql = "INSERT INTO tblAuditLog (Action, Msg) " &_ "VALUES ('Insert the man', 'Brian Berry')" cmd1 = New SqlCommand(sql, conn) Next, we set the command to be a part of the transaction. cmd1 is now within the Tran transaction object.
cmd1.Transaction = Tran We then attempt to execute the command. If this takes place successfully, we should display the message below.
cmd1.ExecuteNonQuery() Response.Write("Audit Log entry added<br/>") Now we create another command and attempt to execute it. Notice that we assign cmd2 to the same Tran transaction object.
sql = "INSERT INTO Employees (FirstName, LastName) " & _ "VALUES (@FirstName, @LastName)" cmd2 = New SqlCommand(sql, conn) cmd2.Transaction = Tran cmd2.Parameters.Add("@FirstName", "Brian") cmd2.Parameters.Add("@LastName", "Berry") cmd2.ExecuteNonQuery() If this is successful, we display the message below, and then commit the transaction.
Response.Write("Employee added<br/>") Tran.Commit() But if any of the statements within the Try section above fail, we catch the exception and roll back the transaction, letting the user know there was an error.
Catch Tran.Rollback() Response.Write("There was an error<br/>") Finally, regardless of whether we were successful or not, we need to close the connection.
Finally conn.Close() End Try
To see a performance comparison of the different ways of handling transactions, take a look at this article on MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch13.asp. |