In a previous entry, I discussed writing two different custom transaction managers, local and distributed. The thing that always bothered me about my own implementation was 1) it was my own implementation and 2) You have to choose up front which transaction you should support.
I've been doing some maintenance on my Data Tier Generator project lately, and I decided to revisit how transactions are supported. Version 2.0 of the .NET Framework introduced a new assembly and namespace, System.Transactions. At the time, I couldn't think of a way to leverage the built-in support, but I think I've got it straightened out now.
Using a combination of System.Transactions and System.Threading, I've devised a new method for supporting transactions in my SqlClientUtility class that is part of SharpCore. The Transaction class exposes a property called Current, which can be used to determine if the current code is running in the context of a transaction. Using the Transaction.Current property in conjunction with thread local storage, I've managed to use the built-in transaction support rather than my own. Here's the basic approach I've taken:
if (Transaction.Current == null)
{
string connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
// TODO: Execute the SqlCommand
}
else
{
LocalDataStoreSlot connectionStringNameSlot = Thread.GetNamedDataSlot("ConnectionStringName:" + connectionStringName);
LocalDataStoreSlot connectionSlot = Thread.GetNamedDataSlot("SqlConnection:" + connectionStringName);
SqlConnection connection = null;
string data = (string) Thread.GetData(connectionStringNameSlot);
if (data == null)
{
string connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
connection = new SqlConnection(connectionString);
Thread.SetData(connectionStringNameSlot, connectionStringName);
Thread.SetData(connectionSlot, connection);
}
else
{
connection = (SqlConnection) Thread.GetData(connectionSlot);
}
// TODO: Execute the SqlCommand
}
You could quite easily substitute a different object or set of data for a SqlConnection. And the best thing about this approach is that if the method that calls this code is in a local transaction, it will automatically be promoted to a distributed transaction if required.
Now the only thing I have to do is figure out how to close my connection when I'm done with it...