S
S?ren Mondrup
SqlConnection throws an exception when disposed if it is manually
enlisted in a distributed transaction:
System.InvalidCastException: Specified cast is not valid.
Server stack trace:
at System.Data.SqlClient.ConnectionPool.PutConnectionManualEnlisted(SqlInternalConnection
con)
at System.Data.SqlClient.ConnectionPool.PutDeactivatedConnection(SqlInternalConnection
con)
at System.Data.SqlClient.SqlConnection.Close()
at System.Data.SqlClient.SqlConnection.Dispose(Boolean disposing)
at System.ComponentModel.Component.Dispose()
The error occurs when the transaction is cast to UCOMIConnectionPoint.
I have tried to enlist in a distributed transaction created by a
serviced component and a transaction created by the DTC proxy. Both
yield the same error. The problem seems to be that the transactions
created by my DTC doesn't support IConnectionPoint (I use Windows XP
pro). I guess that the connection point is used to get a notification
when the transaction ends and it is safe to return the connection to
the connection pool.
(OracleClient doesn't fail (the same way). It doesn't need the
connection point. It returns connections directly to the connection
pool when the they are disposed even if they still participate in a
distributed transaction!)
Is this a bug in SqlConnection, the SqlClient connection pool, the DTC
or my code? Below is a short sample that generates the error. I need
to enlist in the distributed transaction manually, because I want to
use distributed transactions without using COM+.
Any suggestions?
Regards Søren Mondrup
-----demo.cs----------------------------------------------
using System;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
[assembly: AssemblyKeyFileAttribute("demo.snk")]
class App
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: demo.exe <server_name>");
return;
}
try
{
string connStr = "Server=" + args[0] +
";Trusted_Connection=SSPI;Database=pubs;Enlist=false;";
using (ServicedFoo foo = new ServicedFoo())
{
foo.DoTransaction(connStr);
}
}
catch(System.Exception e)
{
Console.WriteLine(e);
}
}
}
[Transaction(TransactionOption.RequiresNew)]
public class ServicedFoo : ServicedComponent
{
public void DoTransaction(string connStr)
{
ITransaction tx = (ITransaction) ContextUtil.Transaction;
if( tx == null )
throw new Exception("Failed to create transaction.");
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
conn.EnlistDistributedTransaction(tx);
Console.WriteLine("Enlisted in transaction");
using (SqlCommand cmd = new SqlCommand("update sales set qty =
qty + 1", conn))
{
cmd.ExecuteNonQuery();
}
ContextUtil.SetComplete();
Console.WriteLine("Has voted in favor of commit. Now disposing
connection.");
} // conn.Dispose() is called and an exception is thrown
}
}
enlisted in a distributed transaction:
System.InvalidCastException: Specified cast is not valid.
Server stack trace:
at System.Data.SqlClient.ConnectionPool.PutConnectionManualEnlisted(SqlInternalConnection
con)
at System.Data.SqlClient.ConnectionPool.PutDeactivatedConnection(SqlInternalConnection
con)
at System.Data.SqlClient.SqlConnection.Close()
at System.Data.SqlClient.SqlConnection.Dispose(Boolean disposing)
at System.ComponentModel.Component.Dispose()
The error occurs when the transaction is cast to UCOMIConnectionPoint.
I have tried to enlist in a distributed transaction created by a
serviced component and a transaction created by the DTC proxy. Both
yield the same error. The problem seems to be that the transactions
created by my DTC doesn't support IConnectionPoint (I use Windows XP
pro). I guess that the connection point is used to get a notification
when the transaction ends and it is safe to return the connection to
the connection pool.
(OracleClient doesn't fail (the same way). It doesn't need the
connection point. It returns connections directly to the connection
pool when the they are disposed even if they still participate in a
distributed transaction!)
Is this a bug in SqlConnection, the SqlClient connection pool, the DTC
or my code? Below is a short sample that generates the error. I need
to enlist in the distributed transaction manually, because I want to
use distributed transactions without using COM+.
Any suggestions?
Regards Søren Mondrup
-----demo.cs----------------------------------------------
using System;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
[assembly: AssemblyKeyFileAttribute("demo.snk")]
class App
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("Usage: demo.exe <server_name>");
return;
}
try
{
string connStr = "Server=" + args[0] +
";Trusted_Connection=SSPI;Database=pubs;Enlist=false;";
using (ServicedFoo foo = new ServicedFoo())
{
foo.DoTransaction(connStr);
}
}
catch(System.Exception e)
{
Console.WriteLine(e);
}
}
}
[Transaction(TransactionOption.RequiresNew)]
public class ServicedFoo : ServicedComponent
{
public void DoTransaction(string connStr)
{
ITransaction tx = (ITransaction) ContextUtil.Transaction;
if( tx == null )
throw new Exception("Failed to create transaction.");
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
conn.EnlistDistributedTransaction(tx);
Console.WriteLine("Enlisted in transaction");
using (SqlCommand cmd = new SqlCommand("update sales set qty =
qty + 1", conn))
{
cmd.ExecuteNonQuery();
}
ContextUtil.SetComplete();
Console.WriteLine("Has voted in favor of commit. Now disposing
connection.");
} // conn.Dispose() is called and an exception is thrown
}
}