Ok, this is pretty crazy. I'm on XPSP2 and I've entered a ServiceDomain with a ServiceConfig that requires transactions with an IsolationLevel of Serializable. Now, I've done this a million times before and it works no problem. In the specific scenario I'm dealing with now, I'm seeing something completely insane. I've constructed a SqlConnection and I'm getting ready to Open it. Up until I call Open ContextUtil.IsInTransaction is true. Now I step over the call to Open and *bam* I've lost my COM+ context. ContextUtil.IsInTransaction is now false and any attempt to SetComplete or SetAbort throws:
System.Runtime.InteropServices.COMException (0x8004E004): There is no MTS object context at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo) at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode) at System.EnterpriseServices.Thunk.ContextThunk.SetComplete() at System.EnterpriseServices.ContextUtil.SetComplete()
No exceptions are being thrown from within the call to Open and I can run another project that does the same transaction/connection logic to the same SQL server instance, which is just a local instance on my dev box anyway, and use distributed transactions there just fine. Therefore I know my MSDTC is properly configured and working.
I've cross-posted this to the ADVANCED DOTNET list that DM hosts. Hopefully I'll get an answer from someone soon, 'cause I'm completely perplexed by this one.
Update:
Ok, so I dug a little deeper. I found out exactly where inside of SqlConnection::Open that my context is being blown away. Have a look at this:
system.enterpriseservices.dll!System.EnterpriseServices.Platform.Initialize() + 0x117 bytes
system.enterpriseservices.dll!System.EnterpriseServices.Platform.get_W2K() + 0x9 bytes
system.enterpriseservices.dll!System.EnterpriseServices.ResourcePool.ResourcePool(System.EnterpriseServices.ResourcePool.TransactionEndDelegate cb = {System.EnterpriseServices.ResourcePool.TransactionEndDelegate}) + 0x1b bytes
system.data.dll!System.Data.SqlClient.ConnectionPool.CreateResourcePool() + 0x40 bytes
system.data.dll!System.Data.SqlClient.ConnectionPool.ConnectionPool(System.Data.SqlClient.DefaultPoolControl ctrl = {System.Data.SqlClient.SqlConnectionPoolControl}) + 0x1c2 bytes
system.data.dll!System.Data.SqlClient.PoolManager.FindOrCreatePool(System.Data.SqlClient.DefaultPoolControl ctrl = {System.Data.SqlClient.SqlConnectionPoolControl}, int SID = 2066800) + 0xb8 bytes
system.data.dll!System.Data.SqlClient.SqlConnectionPoolManager.GetPooledConnection(System.Data.SqlClient.SqlConnectionString options = {System.Data.SqlClient.SqlConnectionString}, bool isInTransaction = false) + 0x141 bytes
system.data.dll!System.Data.SqlClient.SqlConnection.Open() + 0xce bytes
So what this looks like is, even though I've already entered a ServiceDomain prior to opening the SqlConnection, this particular code path somehow reinitializes .NET's enterprise services layer whacking the current COM+ state. Can you say BUG!? :\
Well I'll go ahead and submit this as a bug, but the workaround would seem to be to open a dummy SqlConnection at the very beginning of your application (perhaps for each distinct connection string since each one creates a new pool??) to force this scenario to happen so that future calls to open a connection, which may actually need the connection to be hooked up, do not have to run through this code path.