C# Transactions
As good developers, we all try to handle exceptions as holistically as possible. I get quite annoyed when I am on a site and some action results in the ugly red ASP.Net error screen. This article talks about handling multiple errors and how to roll them back within a transaction.
Let's say we are creating a new user account. The process to create a new user account is
- Create the User (returning the userId
- Add the User to some generic Roles (using the userId from step 1)
- Give the user a default password
- Send the user an email with the password and URL details so they can login
We could just do something like this
int userId = UserRepository.CreateUser(userName, firstName, lastName, emailAddress);
RolesRepository.AddToRole(userId, roleId);
string encryptedPassword = Encrypt("#$*&*#*");
AuthenticationRepository.SetPassword(userId, encryptedPassword);
SendMail(emailAddress, emailBody);
That would be fine, right? Well, no. What happens if there is an error in the method that sets the password. That means we have a User record with roles associated, but that record has no authentication and no-one has received an email with any details. It's lost in the system.
What we really need to do is make sure that all previous steps are successful before finishing the process. And we do that by using a System class called TransactionScope
. It's been around since ASP.Net 2.0, so it's not new. I will use the example from above and incorporate the TransactionScope class. NB You may need to set a project reference to the assembly to get access to the Namespace - System.Transactions
.
try {
using (TransactionScope scope = new TransactionScope()){
int userId = UserRepository.CreateUser(userName, firstName, lastName, emailAddress);
RolesRepository.AddToRole(userId, roleId);
string encryptedPassword = Encrypt("#$*&*#*");
AuthenticationRepository.SetPassword(userId, encryptedPassword);
SendMail(emailAddress, emailBody);
scope.Complete();
}
} catch (Exception ex) {
System.Diagnostics.Trace.Write(ex.Message);
return false;
}
All I did was wrap the entire code block in the using statement. Doing this means that everything that falls within that using statement will be handled in one sequential transaction. You will notice the scope.Complete()
statement. This commits and finalises the transaction. If that method is not called, the transaction fails and rolls back.
Secondly, I wrapped the using statement in a try/catch block to handle the exception. This means that an exception will jump out of the transaction to the catch block - therefore, never hitting the scope.Complete()
method call. And that's how we roll back. The .Net framework does it all for us.
I hope that helps. Feel free to comment.
Til next time ...