Keeping a Transaction from Escalating

An entry about ado.net Publication date 20. December 2007 20:32

With the introduction of the System.Transactions namespace in .Net 2.0, working with transactions became dead easy. All you need to do is wrap the section of code you want to be transacted in a TransactionScope, and the framework will take care of the rest. There's one problem though - if you open more than one database connection during the scope of the transaction, then the transaction escalates to a two-phase transaction, which means enlisting the help of the the Microsoft Distributed Transaction Coordinator service. Apart from the obvious overhead of communicating with this unmanaged service on your database server, there's another reason for wanting to avoid this - it might not be running. If you're hosting your application on a shared web host for instance, chances are their database server will have the MDTC disabled, and chances are they won't enable it for you either. So what can a poor programmer do then?

He can be clever about reusing the connections when working inside a transaction. This article will show how we can write a custom DbProviderFactory proxy that intercepts any CreateConnection() calls and reuses connections while inside a transaction scope. The beauty of this implementation is that, as long as you're using the DbProviderFactories (which you really should!) to create your connections, it will be totally transparent to your code. This is important because a transaction might be a high level concept in your code, far removed from the data access layer.

DbProviderFactory, Please

By implementing our data access layer so that it always queries the current DbProviderFactory for a connection, then there is one central place where connections are created, vastly simplifying what we want to do. Imagine then, that we have a DataStore that looks something like this:

public class DataStore
{
private string _connectionString;
private DbProviderFactory _dbProviderFactory;
public static readonly DataStore Instance = new DataStore();
private DataStore()
{
_dbProviderFactory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings["Iridescence.Data.DataStore"].ProviderName);
_connectionString = ConfigurationManager.ConnectionStrings["Iridescence.Data.DataStore"].ConnectionString;
}
// datastore methods here...
}

This is in fact the constructor from my DataStore API (simplified for clarity), which you can read more about here. Notice how we load up a DbProviderFactory by reading a few configuration settings, and storing them for use later. Our app.config looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="Iridescence.Data.DataStore" providerName="System.Data.SqlClient" connectionString="..."/>
</connectionStrings>
</configuration>

The providerName attribute tells the DataStore which factory to use - in this case the SqlClient factory, which is the Ado.Net factory for Sql Server. Now, whenever the DataStore needs a connection, all it has to do is call the CreateConnection() method on the factory. Having our DataStore implemented in this way enables us to ensure there is one central place where connections are created - and more importantly, a plugable one which we can substitute the implementation of.

Implementing a Transaction Aware DbProviderFactory

Having this extensibility point in our code, we can then take advantage of it to incorporate connection reuse into the DataStore. All we need to do, is write a custom DbProviderFactory that knows when CreateConnection should create a new connection, and when it should keep returning the same instance several times to reuse an existing connection. Here's the code we need:

public sealed class DbConnectionScope : DbProviderFactory
{
private readonly DbProviderFactory _realFactory;
private readonly Dictionary<Transaction, DbConnection> _transactionConnections = new Dictionary<Transaction, DbConnection>();
///<summary>
    /// Creates a new instance of the DbConnectionScope class
    ///</summary>
    ///<param name="factoryToScope">The factory to scope</param>
    public DbConnectionScope(DbProviderFactory factoryToScope)
{
_realFactory = factoryToScope;
}
///<summary>
    ///Returns a new instance of the provider's class that implements the <see cref="T:System.Data.Common.DbConnection"></see> class.
    ///</summary>
    ///
    ///<returns>
    ///A new instance of <see cref="T:System.Data.Common.DbConnection"></see>.
    ///</returns>
    ///<filterpriority>2</filterpriority>
    public override DbConnection CreateConnection()
{
Transaction transaction = Transaction.Current;
// we're not currently in a transaction, so we just let the real factory work normally
        if(null == transaction)
{
return _realFactory.CreateConnection();
}
else // we're in a transaction, so we want to ensure connection reuse
        {
DbConnection connection;
if (!_transactionConnections.TryGetValue(transaction, out connection))
{
// this is the first time this transaction requests a connection. 
                // Lets create it, wrapping it in a ScopedConnectionProxy which will 
                // ensure that any call to Close does not result in actually closing the connection 
                // (because we want to reuse the open connection throughout the entire transaction scope)
                connection = new ScopedConnectionProxy(_realFactory.CreateConnection());
_transactionConnections.Add(transaction, connection);
// we need to know when the transaction ends, so we can close the connection.
                transaction.TransactionCompleted += transaction_TransactionCompleted;
}
return connection;
}
}
void transaction_TransactionCompleted(object sender, TransactionEventArgs e)
{
DbConnection connection;
// transaction has ended, remove it from the cache and ensure it is disposed
        if(_transactionConnections.TryGetValue(e.Transaction, out connection))
{
_transactionConnections.Remove(e.Transaction);
connection.Dispose();
}
}
}

We've implemented our factory using the transparent proxy pattern, so that it wraps the actual DbProviderFactory and extends it with connection reuse functionality whenever a transaction is present. There is one problem left to tackle however, and that is keeping the connection alive - because most likely, the DataStore will do the following whenever it requests a connection:

using (IDbConnection connection = _dbProviderFactory.CreateConnection()
{
using (IDbCommand command = connection.CreateCommand())
{
// prepare and execute command
    }
}

This is of course a good thing as it ensures we don't leave any connections lying around - they're scarce resources, after all - but it causes a problem for our factory; the connection will be closed and disposed, so when we return the same instance the next time, it won't be very reusable, which kinds of defeats what we were trying to accomplish... Resorting to the transparent proxy pattern again, we can solve this by wrapping the connection returned in a proxy that ensures that any call to Close and Dispose is only effective if the transaction has ended:

private class ScopedConnectionProxy : DbConnection
{
private readonly DbConnection _realConnection;
public ScopedConnectionProxy(DbConnection realConnection)
{
_realConnection = realConnection;
}
/// <summary>
    /// Closes the connection if connection reuse is disabled or we're not currently inside a transaction.
    /// </summary>
    public override void Close()
{
Transaction transaction = Transaction.Current;
if (null == transaction || transaction.TransactionInformation.Status != TransactionStatus.Active)
{
_realConnection.Close();
}
}
protected override void Dispose(bool disposing)
{
Transaction transaction = Transaction.Current;
// only dispose if transaction has ended. Otherwise, 
        //transaction end event will dispose the connection for us
        if (null == transaction || transaction.TransactionInformation.Status != TransactionStatus.Active)
{
_realConnection.Dispose();
base.Dispose(disposing);
}
}
}

And there you have it. The only change needed in our DataStore to active the connection reuse, is to wrap the DbProviderFactory in our proxy:

private DataStore()
{
_dbProviderFactory = DbProviderFactories.GetFactory(ConfigurationManager.ConnectionStrings["Iridescence.Data.DataStore"].ProviderName);
_dbProviderFactory = new DbConnectionScope(_dbProviderFactory);
_connectionString = ConfigurationManager.ConnectionStrings["Iridescence.Data.DataStore"].ConnectionString;
}

Why Transactions Escalate

To end off, I'd like to point out that this solution does not guarantee that the transaction will not escalate. What really happens when you open up a second connection, is that a second entity takes part in the transaction, and this is the reason for the requirement to escalate it. As long as there's only one entity involved, that entity can take ownership of the transaction and coordinate it, but when more than one entities are involved a distributed transaction coordinator is needed to hold the reins. For the most part when dealing with the kind of transactions discussed here, which relate to updating stuff in the database, the connections are the only entities that will be involved. But its perfectly possible to introduce other entities - for instance you might write your own in-memory object transaction manager by implementing the IEnlistmentNotification interface and enlisting it with the transaction to ensure that a roll-back also reverts the object to keep it in synch with the database. That might be a good post for later :)

Get the Source Code

You can download my DbConnectionScope implementation here. I threw this together in half an hour this evening, so its not really been tested thoroughly. In particular, I've not really implemented the DbProviderFactory proxy fully, and only focused on the CreateConnection method. Look out for a follow-up post on this topic later, which will also include the DataStore API update that takes advantage of the proxy.

Oh yes, and in case this is the last post this year, I'd like to wish everyone a merry Christmas and a happy new year! Cheers :)

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

The DataStore API: Part One

An entry about ado.net Publication date 6. April 2007 12:11

At the Norwegian .NET User Group Vestfold (NNUG Vestfold) meeting that we hosted on the 29th of March, I did a presentation on how to develop a lightweight O/R mapper in .NET. During the meeting I promised to post the source code for the DataStore API that I had written, and here it is (download link at the end of this post)!

The API is mostly finished, but there may still be some bugs to iron out, and there are a few improvements here and there that I hope to get around to doing. I have already deployed it in the backend of the blog engine that is running on this site however, and it is doing its job perfectly so far :)

In this first post, I'll be giving a quick introduction to how it works, concentrating on the basic features exposed by it. In the coming weeks, I will be writing some focused posts on interresting parts of the implementation behind the API, so watch out for that :)

Preparing Your Types

At the core of the API sits the DataStore class. It contains all the methods for saving and retrieving objects to/from the data base. In order to use the DataStore however, you need to adorn the types in your object model with some attributes first. Basically, any property that you want to be persisted in the database needs to be decorated with a ColumnDefinitionAttribute. Optionally, the class itself may be decorated with a TableDefinitionAttribute, but this is only necessary if you wish to map the type to a table whose name does not match the class name (if so, use the Alias property of the attribute). An example type definition would look like this:

public class Item
{
private Int64 _id;
private string _name;
[ColumnDefinition(IsPrimaryKey=true, IsAutoIncrement=true)]
public Int64 Id
{
get { return _id; }
private set { _id = value; }
}
[ColumnDefinition]
public string Name
{
get { return _name; }
set { _name = value; }
}
}

This type then, will be able to store and retrieve data to/from a table named "Item" in your database that looks like this:

Item table in database

(Note: The Id property is configured as an auto-incrementing identity).

Currently, the API does not have any support for creating the database, so you need to create all the tables manually. The thing to keep in mind is that either the table name and colum names must match the class name and property names (case and all), or you must specify the table/column names using the alias properties. And of course the type of the column must match the type of the property (unless you have specified a TypeMapper and DbType in the ColumnDefinition - more on that in a later post).

Using the DataStore

With both our type and tables defined, we can then use the DataStore to do the usual SQL stuff, for example saving a new object to the database:

DbProviderFactory factory = DbProviderFactories.GetFactory("System.Net.SqlClient");
DataStore store = new DataStore(factory, "Data Source=.\SQLEXPRESS; Initial Catalog=MyDatabase; Integrated Security=True");
Item item = new Item();
item.Name = "My item";
item.Id = Convert.ToInt64(store.Save(item));

Two things to pay attention to here: Currently, the DataStore is only compatible with Sql Server 2005, which means that if you try to pass it a DbProviderFactory other than the one above, it will throw an exception. Secondly, notice how the Save method returns the id that the database assigned to the newly created row in the Item table, since I specified the primary key as an auto-incrementing identity.

While saving is fairly straight-forward, selecting has a few more options. The API has support for both filtering and paging, and can even run a custom SQL statement that you provide. Lets look at a few select examples:

foreach (Item item in store.Select<Item>("Name", Operator.Like, "f%"))
{
Console.WriteLine(item.Name);
}

In the example above, we are selecting all Items that have a Name that starts with the letter 'f'. Basically, this Select translates into the query "SELECT * FROM Item WHERE [Name] LIKE '%f'. Notice that the Select method returns a custom iterator - someone raised a concern that this would leave the connection to the database open, but as long as the iterator is disposed of after use - which the foreach construct does automatically - this is not an issue. I'll go into detail on this in a later post, as it is quite interresting and obviously has an impact on best-practices for the use of Select<T>.

List<Item> results = new List<Item>(store.Select<Item>(new Action<IDbCommand>(
delegate(IDbCommand command)
{
command.CommandText = "SELECT * FROM Item WHERE Id IN(1,2,3)";
})));

In this example, I've used the Select<T> overload that acceps an Action<IDbCommand> delegate, which lets me prepare the command object manually. This is a powerfull method where I can (as above) set a custom SQL statement to be executed, register parameters and so forth. Notice that I only need to prepare the command - the DataStore will then internally execute my command and read out the data it returns and populate the respective properties. This method truly shows the power of both Generics and anonymous delegates :)

In addition to the Save and Select methods, there are several other members of the DataStore that expose all the usual CRUD functionality that I wont go into detail explaining here, as they all function more or less in the same way.

The Source Code

So I bet you are itching to get your hands on the code... get it here :)

Be the first to rate this post

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS