Click here to Skip to main content
Click here to Skip to main content

Easier Database Transactions - Extending the Using Statement to Perform Automatic Database Transactions

By , 25 Oct 2004
 

Overview

Any user who works with database updates uses transactions. Transactions in ADO.NET are done using a transaction object, and a try..catch, but there is an easier, one might even say a more C# way to handle database transactions.

Visual Basic .NET Users

Because this technique relies on the using statement which is C# specific, this technique cannot be applied in Visual Basic .NET. However, rumors are that Visual Studio .NET 2005 will include a similar statement for Visual Basic.

Transactions the Normal Way

Here is an example of a database transaction the normal way. This is a condensed version of the example on MSDN.

using (OleDbTransaction xTx = _DB.BeginTransaction()) {
    try {
        OleDbCommand xCmd = _DB.CreateCommand();
        xCmd.Transaction = xTx;
        xCmd.CommandText = "Insert into Person (Name, Telephone)" + 
                           " values ('Normal One', '(412) 555-1212')";
        xCmd.ExecuteNonQuery();
        if (chckThrow.Checked) {
            throw new Exception("Test Exception");
        }
        xCmd.CommandText = "Insert into Person (Name, Telephone)" + 
                           " values ('Normal Two', '(423) 555-1212')";
        xCmd.ExecuteNonQuery();
        xTx.Commit();
    }
    catch (Exception xException) {
        xTx.Rollback();
        throw xException;
    }
}

Transactions the Easy Way

With my method, the code does the same, but becomes a bit shorter, cleaner, and even a bit more C# like.

using (Transaction xTx = new Transaction(_DB)) {
    OleDbCommand xCmd = _DB.CreateCommand();
    ((IDbCommand)xCmd).Transaction = xTx.DBTransaction;
    xCmd.CommandText = "Insert into Person (Name, Telephone)" + 
                       " values ('Object One', '(412) 555-1212')";
    xCmd.ExecuteNonQuery();
    if (chckThrow.Checked) {
        throw new Exception("Test Exception");
    }
    xCmd.CommandText = "Insert into Person (Name, Telephone)" + 
                       " values ('Object Two', '(423) 555-1212')";    
    xCmd.ExecuteNonQuery();
    xTx.Commit();
}

Demo Project

The demo project demonstrates this and allows simulated exceptions. The demo project uses the included Access database file for simplicity. However, the included class works with any ADO.NET data source.

How it Works

The using statement in C# causes the dispose to be called when the block is exited. By implementing a Dispose, and checking to see if the transaction has been committed, the transaction class can then determine whether or not to call rollback. There is no need to rethrow any exception that may be in progress, because C# automatically continues the exception path as part of the handling of the using block.

Transaction Class Source

public class Transaction : IDisposable {

    private IDbConnection _DB;
    private IDbTransaction _DBTransaction;

    public IDbTransaction DBTransaction {
      get { return _DBTransaction; }
    }

    public Transaction(IDbConnection aDB) : this(aDB, true) {
    }

    public Transaction(IDbConnection aDB, bool aHandle) {
      if (aHandle) {
        _DB = aDB;
        _DBTransaction = _DB.BeginTransaction();
      }
    }

    public void Commit() {
      if (_DB != null) {
        _DBTransaction.Commit();
        _DBTransaction.Dispose();
        _DB = null;
      }
    }

    public void Dispose() {
      if (_DB != null) {
        _DBTransaction.Rollback();
        _DBTransaction.Dispose();
        _DB = null;
      }
    }
}

Conclusion

This class purely adds syntactic sugar to your code and makes performing transactions a little bit easier. The functionality is the same. But when working with transactions and having to manually create this structure each time, the shorter version is a lot easier to type, as well as read later when working in the code again.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Chad Z. Hower aka Kudzu
Other
Cyprus Cyprus
Member
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
www.KudzuWorld.com
 
Formerly the Regional Developer Adviser (DPE) for Microsoft Middle East and Africa, he was responsible for 85 countries spanning 4 continents and 10 time zones. Now Chad is a Microsoft MVP.
 
Chad is the chair of several popular open source projects including Indy and Cosmos (C# Open Source Managed Operating System).
 
Chad is the author of the book Indy in Depth and has contributed to several other books on network communications and general programming.
 
Chad has lived in Canada, Cyprus, Switzerland, France, Jordan, Russia, Turkey, and the United States. Chad has visited more than 60 countries, visiting most of them several times.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralDifferent implementation of the transaction [modified]memberPIEBALDconsult17 Mar '08 - 11:26 
I'd have the custom transaction class implement IDbTransaction.
 
... and not provide access to the wrapped Transaction.
 
modified on Monday, March 17, 2008 5:43 PM

GeneralRe: Different implementation of the transactionmemberChad Z. Hower aka Kudzu17 Mar '08 - 13:31 
I wrote this article when .NET 1.1 was current, IIRC that interface did not exist then. In .NET 2.0 a LOT of data classes got much needed interfaces as some were missing in 1.1.
 
Also since 2.0, there is a whole methodology of transactions built into .NET.
 
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

QuestionClose?sussAnonymous31 Oct '04 - 12:39 
What about closing the connection?
AnswerRe: Close?memberChad Z. Hower aka Kudzu31 Oct '04 - 14:12 
You dont want to automatically close the connection. Most connections are reused many times and perform many transactions.
 
In SOME environments it happens that 1 transaction = 1 connection, but thats not the rule. If yours is such as this a property certainly could be added to make this behaviour optional.

 
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.hower.org/Kudzu/
 
My Software Blog:
http://blogs.atozed.com/kudzu/category/11.aspx

AnswerRe: Close?memberAlvaro Mendez27 May '05 - 9:50 
Anonymous wrote:
closing the connection
 
Yep, that's what I do:
 
using (SqlConnection conn = GetTheConnectionSomehow())
{
  SqlTransation trans = conn.BeginTransaction();
 
  ....
 
  trans.Commit(); 
}
Two birds with one stone. Smile | :)
 
Regards,
Alvaro
 

Victory means exit strategy, and it's important for the President to explain to us what the exit strategy is. -- GWB, 1999.
GeneralNo need to wrap to shorten the codesussCandan Akyol26 Oct '04 - 9:46 
ADO.NET transactions can work exactly the same way as your transaction class. the IDbTransaction interface (which is the interface for all ADO.NET provider transactions) implement IDisposable, and AFAIK (at least for SqlTransaction), when disposed, they are rolled back if not commited. The following snippet is a pure ADO.NET code that makes it "easier":
 

SqlConnection connection = new SqlConnection("server=(local);integrated security=sspi;");
using (SqlTransaction transaction = connection.BeginTransaction())
{
// do some work here with commands attached to the transaction
transaction.Commit();
}
 
// no need to catch exceptions and rollback, transaction will be disposed in an exception case and will be rolled back automatically

GeneralRe: No need to wrap to shorten the codememberChad Z. Hower aka Kudzu27 Oct '04 - 2:18 
Yes, SQL transactions do this. However nowhere in ADO.NET does it describe this as required, or even default behaviour. Thus you should not rely on it. Also how can you guarantee all data providers will implement this same behaviour?
 
By implementing it explicitly we guarantee the behaviour. As it is, some data providers may choose to throw an exception - that is require the user to call rollback or commit, or they could choose to do something else.

 
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.hower.org/Kudzu/
 
My Software Blog:
http://blogs.atozed.com/kudzu/category/11.aspx

GeneralThe pattern is almost right...membercasperOne26 Oct '04 - 3:06 
This is a good step in the right direction, but it could use some tweaking.
 
The first problem is the calling of Dispose on the transaction in the Commit and Rollback sections. I don't agree that it should be done there. You are just incurring extra overhead for something that is going to be called anyways in the Dispose method.
 
Also, there should be no reason to call Rollback explicitly. If a transaction is not committed, it should rollback automatically. This is inherent in their nature, and their semantics should not have to be duplicated in code.
 
Support for nested transactions would be nice.
 
As an optimization, I would change this to a structure that has a public Dispose method and does not implement IDisposable. You can still use it in a using block, and it will give you a perf boost.
 
You should take a look at the System.Transactions space in .NET 2.0, as it will show you the new model for transactions in .NET. Specifically, take a look at the TransactionScope class.
 
All in all, a good effort though.
 
- Nicholas Paldino [.NET/C# MVP]

GeneralRe: The pattern is almost right...memberChad Z. Hower aka Kudzu27 Oct '04 - 2:42 
casperOne wrote:
This is a good step in the right direction, but it could use some tweaking.
 
Of course it could - its just a simple example.
 
casperOne wrote:
The first problem is the calling of Dispose on the transaction in the Commit and Rollback sections. I don't agree that it should be done there. You are just incurring extra overhead for something that is going to be called anyways in the Dispose method.
 
This is purely a matter of prerference. I choose to get rid of "important" objects sooner rather than later. You could remove them.
 
casperOne wrote:
As an optimization, I would change this to a structure that has a public Dispose method and does not implement IDisposable. You can still use it in a using block, and it will give you a perf boost.
 
Im not following you. At first I thought you were saying that if Dispose is public, then IDisposable is not needed and it would find it through reflection. However in my actual class (not this sample) Dispose is already public. I tried to remove IDisposable from the class, and when I do this it refuses to compile. It says it cannot convert type to System.Disposable. Can you elaborate more please?
 
casperOne wrote:
You should take a look at the System.Transactions space in .NET 2.0, as it will show you the new model for transactions in .NET. Specifically, take a look at the TransactionScope class.
 
Yes. I've taken a look at it on your cue. In fact its very nice and very similar to how I would have done it had I had access to deeper compiler items. But we are just users and in 1.1 so we cannot do compiler magic to extend it. So we are limited - but the technique here is pretty neat for 1.1 and I hope it shows other users to think out of the box a bit and that the using statement can actually be used to "Add" or extend functionality.
 
casperOne wrote:
All in all, a good effort though.
 
Thanks!
 
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.hower.org/Kudzu/
 
My Software Blog:
http://blogs.atozed.com/kudzu/category/11.aspx

QuestionIs shorter always better?memberMark Focas25 Oct '04 - 12:13 
I think the first code is better, in that if I am reading someone elses code, I can clearly see their reasoning. Having an explicit call to rollback() means I know the developer knew what they were doing, and the motivation behind it, that they actually thought it thru. Having it done automatically means maybe a bad coder may have rollback called without them realizing it, and it may be harder to debug such code.
I do like your idea of encapsulation. Maybe if the class had a name that reflected the fact that it handles roolbacks automatically, the code may be clearer?
 
Being in a minority of one, doesn't make you insane
George Orwell
However, in my case it does

AnswerRe: Is shorter always better?memberChad Z. Hower aka Kudzu25 Oct '04 - 12:25 
Shorter is not always better. That wasnt the motivation for this technique. In some cases it may not fit and you can use the other one. However it is about patterns - 99.9% of the time I was building exactly as the first one, and this saves me time and is much clearer when that pattern fits, which is 99.9% of the time.
 
Keep in mind - it ONLY rolls back if there is an unhandled error. You certainly could handle the error inside and a method could be added to tell the object not to rollback. But you will alwasy need to do something in those 0.01% cases.
 
This is just a sample object, it certainly can be expanded and is in fact just a small extraction of a much larger DB library (one might think of it as ADO.NET Plus) that I am using.

 
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.hower.org/Kudzu/
 
My Software Blog:
http://blogs.atozed.com/kudzu/category/11.aspx

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 25 Oct 2004
Article Copyright 2004 by Chad Z. Hower aka Kudzu
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid