Extending Lightweight Transactions in SqlClient

Published 26 March 08 03:43 AM | alazela 

We’ve done some work with SqlClient’s System.Transaction support for Sql Server 2008. To explain what we did, and why, I’m going setup some background first, which may also be useful for understanding the existing behavior.

System.Transactions offers a simple, intuitive model for controlling transactions in your .Net code, right? I generally agree with that statement, but there are some devious details, specifically with regard to distributed transactions. They require special setup on the client. They are slow. And probably a few other issues depending on whom you ask.

With version 2.0, SqlClient allowed lightweight transactions when connecting to Sql Server 2005, alleviating the problem for a group of scenarios. But this introduced a new issue – promotion. Promotion is a good thing, as long as you are happy with distributed transactions, but it can be confusing as to when and why it happens.

So why does the transaction promote? A lightweight transaction is an agreement between SqlClient and the transaction, where SqlClient manages transacting the work on the transaction’s behalf.  SqlClient starts a local server transaction when enlisting into the transaction, and the transaction sends a request to rollback or commit the work at the end. When a second resource tries to enlist in the connection, the local server transaction cannot be used with it, so the transaction promotes. Ah, you say, that second request may be going to the same server, and with connection pooling I should be able to open another connection and use the same transaction!

To understand why this doesn’t work, let’s take a look inside connection pooling. A connection consists of two parts: the public instance that your code interacts with (the outer connection) and a hidden connection that represents an actual server connection (the inner connection). When you “Open()” the outer connection, it looks for a free inner connection from the pool that is associated with the transaction (or creates a new one if it cannot find one). When you “Close()” the outer connection, it returns the inner connection to the pool. The server doesn’t distinguish between uses of the inner connection so only one outer connection can be used with any one inner connection at a time, and the inner connection must be reset to prevent state from showing up on subsequent uses. Resetting closes cursors, changes set options back to defaults, switches the database context back to the one from the connection string AND… rolls back any outstanding local transactions. Resetting this way provides a known starting state every time you open a connection, which would lead to nasty random behavior in many cases, but it also prevents re-using the original inner connection with the lightweight transaction until said transaction completes. When you close it’s outer connection, this inner connection is set aside pending the rollback or commit request. So the second time you open any outer connection, including the original, the pool is empty, and a new inner connection is created. A quick example:

You open outer connection “A”. The pool has no free appropriate connection, so inner connection “z” is set up and enlisted in the transaction, establishing a lightweight transaction. You now close “A”, which sets aside “z” to wait for the transaction to end. Next you open outer connection “B” (you could also open “A” again and get the same results). “B” looks for a free inner connection in the pool attached to the transaction, doesn’t find one, creates inner connection “y” and tries to enlist it in the transaction. The transaction, now finding two different resources trying to enlist, must promote (resources in general, and sql connections in particular, cannot share local transactions). Finally you end the transaction, which sends the commit or rollback across “z”, disconnects it from the transaction and returns it to the pool.

So this brings us to the extensions we added for Sql Server 2008 support. On the server, we added a new connection reset mode that does not roll back local transactions. This allows SqlClient to return the inner connection to the pool to be reused. In our example, when you open “B”, it will finds “z” waiting in the pool, associated with the transaction where “A” put it when you closed “A”. “B” appropriates and resets “z” (with the transaction-preserving reset) and happily continues working. Neither System.Transaction nor the server are aware that the application sees “z” as two separate connections. As far as they are concerned, there is only one connection, working on a single local transaction and no promotion is necessary.

There are still limitations with this approach.  If you try to open a second outer connection BEFORE closing the first one, there won’t be a free connection in the pool, so a second inner connection will have to be enlisted (“A” is using “z” so “B” must again obtain “y”). Ditto if you open a connection with pooling turned off or a slightly different connection string, since neither case will find the original inner connection, even if it is sitting idle in the pool.

The new capability is already in SqlClient v2.0 SP1 (e.g. Visual Studio 2008, Orcas, etc), but requires the changes in Sql Server 2008 to work (CTP5 or later). It is used automatically whenever it can be, so you don’t need to make changes to your code if you are already using pooling and enlisting (both default behaviors). When used against Sql Server 2005, the pooling behavior reverts to that of SqlClient v2.0 RTM.

Alazel Acheson
Developer, ADO.NET 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# MSDN Blog Postings » Extending Lightweight Transactions in SqlClient said on March 26, 2008 12:43 AM:

PingBack from http://msdnrss.thecoderblogs.com/2008/03/26/extending-lightweight-transactions-in-sqlclient/

# Nikolay Vasilyev said on March 26, 2008 10:20 AM:

There are several ways to handle this well known issue with transaction "auto-promotion" on second connection being opened. Like e.g. the one suggested by you with a ConnectionScope class (and the other developers like me create our own envelope classes to bind a TransactionScope instance with a SqlConnection instance).

Should this new approach be somehow more convenient than ConnectionScope? Or is this just an alternative approach suitable for SQL Server 2008 only?

# alazela said on March 26, 2008 1:43 PM:

I'd think a bit of both.  It's more convenient for a range of scenarios, but it is only suitable for use with SQL Server 2008. The scope-like pattern gives you more explicit control over which connection you are executing against and can guarantee you won't promote. But it does requires more rigor and writing more code (although that can generally be encapsulated).

This extension will automatically kick in, even in places you did not (or possibly can not) add scope-like pattern support. Apps using the Visual Studio designer-generated data access patterns, apps using the DataAdapter.Fill()/Update() pattern without opening the connection first, and similar scenarios will gain the benefits.

# Dennis van der Stelt said on March 29, 2008 4:51 PM:

AWESOME!!!!!!!!!!

This was also a very good explanation. Thanks. Too bad it wasn't added to SQL2005 as Nikolay says, but it's in .NET 2.0 so it won't be changed easily. If it ain't broke, don't fix it, huh?! ;-)

I've been waiting for this feature by the way. I've already updated the blogpost! :)

http://bloggingabout.net/blogs/dennis/archive/2007/06/28/system-transactions-still-not-working.aspx

# Other Blogs we recommend (SSQA.net) said on April 5, 2008 7:07 PM:

It appears that there are some changes in .NET 3.5 System.Transactions (or System.Transactions.dll and

# Josh Berke said on April 30, 2008 7:52 PM:

This is a great change in my opinion we went through the hastle of designing a fairly robust TransactionalContext that operated simillarly to both your ConnectionScope and to the System.Transactions TransactionScope. All to avoid promotion.

I've just found your own ConnectionScope implementation, and I have one question / concern with it. I've read a lot about avoiding ThreadStatic variables when working in ASP.Net. Would you recommend your ConnectionScope in such a situation since it stores the Connection object in TLS? And if not are there any other alternatives?

Thanks in advance.

Josh

# Florin Lazar said on May 2, 2008 7:07 PM:

Great news! The new updates added to System.Data and SQL Server 2008 finally allow multiple Open/Close

# Contagious Curiosity said on May 4, 2008 1:57 AM:

Good read which explains updates to SqlClient's System.Transactions in SQL Server 2008 which results

# alazela said on May 8, 2008 11:04 PM:

Josh, I believe the issue with ThreadStatic fields and ASP.Net is due to lifetime management issues outside the scope of the creating code. The TransactionScope/ConnectionScope model avoids those issues by only using the ThreadStatic for nested call scopes, and removing their owned objects when control returns to the originating level.

# Martina said on August 18, 2008 1:49 AM:

We’ve done some work with SqlClient’s System.

# とあるコンサルタントのつぶやき said on January 23, 2009 4:19 AM:

さて、Silverlight 2 や WCF などの最新テクノロジの話ばっかりここまで書いてきたので、たまには地味(けれどもめちゃめちゃ重要)な話をひとつ書いてみたりします。結論を先に書くと、以下の通りです。

Leave a Comment

(required) 
(optional)
(required) 

About alazela

ADO.Net managed providers developer.
Page view tracker