项目涉及到多个数据库的查询更新操作,也就必然需要分布式事务的支持,查了MSDN知道 .net 2.0 中利用新增的 System.Transactions 命名空间可以简单的实现分布式事务:
 System.Transactions 基础结构通过支持在 SQL Server、ADO.NET、MSMQ 和 Microsoft 分布式事务协调器 (MSDTC) 中启动的事务,使事务编程在整个平台上变得简单和高效。它提供基于 Transaction 类的显式编程模型,还提供使用 TransactionScope 类的隐式编程模型,在这种模型中事务是由基础结构自动管理的。强烈建议使用更为方便的隐式模型进行开发。
System.Transactions 基础结构通过支持在 SQL Server、ADO.NET、MSMQ 和 Microsoft 分布式事务协调器 (MSDTC) 中启动的事务,使事务编程在整个平台上变得简单和高效。它提供基于 Transaction 类的显式编程模型,还提供使用 TransactionScope 类的隐式编程模型,在这种模型中事务是由基础结构自动管理的。强烈建议使用更为方便的隐式模型进行开发。参考MSDN的Demo做了SQL Server 2000 的 使用事务范围实现隐式事务测试,事务可以正常提交以及回滚:


 private void Test1()
private void Test1()
 
     {
{ // 使用事务范围实现隐式事务
        // 使用事务范围实现隐式事务 // ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxtransactions/html/1ddba95e-7587-48b2-8838-708c275e7199.htm
        // ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_fxtransactions/html/1ddba95e-7587-48b2-8838-708c275e7199.htm

 using (TransactionScope ts = new TransactionScope())
        using (TransactionScope ts = new TransactionScope())  {
{ //Create and open the SQL connection.  The work done on this connection will be a part of the transaction created by the TransactionScope
            //Create and open the SQL connection.  The work done on this connection will be a part of the transaction created by the TransactionScope SqlConnection myConnection = new SqlConnection("server=(local);Integrated Security=SSPI;database=northwind");
            SqlConnection myConnection = new SqlConnection("server=(local);Integrated Security=SSPI;database=northwind"); SqlCommand myCommand = new SqlCommand();
            SqlCommand myCommand = new SqlCommand(); myConnection.Open();
            myConnection.Open(); myCommand.Connection = myConnection;
            myCommand.Connection = myConnection;

 if (!chkGenError.Checked)
            if (!chkGenError.Checked)  {
{ //Restore database to near it's original condition so sample will work correctly.
                //Restore database to near it's original condition so sample will work correctly. myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
                myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"; myCommand.ExecuteNonQuery();
                myCommand.ExecuteNonQuery(); }
            }
 //Insert the first record.
            //Insert the first record. myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
            myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"; myCommand.ExecuteNonQuery();
            myCommand.ExecuteNonQuery();
 //Insert the second record.
            //Insert the second record. myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
            myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"; myCommand.ExecuteNonQuery();
            myCommand.ExecuteNonQuery();
 myConnection.Close();
            myConnection.Close();

 if (chkCommit.Checked)
            if (chkCommit.Checked)  {
{ ts.Complete();
                ts.Complete(); }
            }
 /**/////Call complete on the TransactionScope or not based on input
            /**/////Call complete on the TransactionScope or not based on input //ConsoleKeyInfo c;
            //ConsoleKeyInfo c; //while (true) {
            //while (true) { //    Console.Write("Complete the transaction scope? [Y|N] ");
            //    Console.Write("Complete the transaction scope? [Y|N] "); //    c = Console.ReadKey();
            //    c = Console.ReadKey(); //    Console.WriteLine();
            //    Console.WriteLine();
 //    if ((c.KeyChar == 'Y') || (c.KeyChar == 'y')) {
            //    if ((c.KeyChar == 'Y') || (c.KeyChar == 'y')) { //        // Commit the transaction
            //        // Commit the transaction //        ts.Complete();
            //        ts.Complete(); //        break;
            //        break; //    }
            //    } //    else if ((c.KeyChar == 'N') || (c.KeyChar == 'n')) {
            //    else if ((c.KeyChar == 'N') || (c.KeyChar == 'n')) { //        break;
            //        break; //    }
            //    } //}
            //} }
        } }
    }
 private void Test2()
    private void Test2()
 
     {
{ string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind";
        string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind"; string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs";
        string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs"; string dt = DateTime.Now.ToString();
        string dt = DateTime.Now.ToString();
 using (TransactionScope transScope = new TransactionScope())
        using (TransactionScope transScope = new TransactionScope())  {
{ using (SqlConnection connection1 = new
            using (SqlConnection connection1 = new
 SqlConnection(connectString1))
               SqlConnection(connectString1))  {
{ // Opening connection1 automatically enlists it in the
                // Opening connection1 automatically enlists it in the  // TransactionScope as a lightweight transaction.
                // TransactionScope as a lightweight transaction. connection1.Open();
                connection1.Open();
 // Do work in the first connection.
                // Do work in the first connection. SqlCommand command1 = new SqlCommand();
                SqlCommand command1 = new SqlCommand();                 command1.Connection = connection1;
                command1.Connection = connection1;
 //Restore database to near it's original condition so sample will work correctly.
                //Restore database to near it's original condition so sample will work correctly. command1.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
                command1.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery();
 //Insert the first record.
                //Insert the first record. command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
                command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery();
 //Insert the second record.
                //Insert the second record. command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
                command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery();
 // Assumes conditional logic in place where the second
                // Assumes conditional logic in place where the second // connection will only be opened as needed.
                // connection will only be opened as needed. using (SqlConnection connection2 = new
                using (SqlConnection connection2 = new
 SqlConnection(connectString2))
                    SqlConnection(connectString2))  {
{ // Open the second connection, which enlists the
                    // Open the second connection, which enlists the  // second connection and promotes the transaction to
                    // second connection and promotes the transaction to // a full distributed transaction.
                    // a full distributed transaction.  connection2.Open();
                    connection2.Open(); 
                    // Do work in the second connection.
                    // Do work in the second connection. SqlCommand command2 = new SqlCommand();
                    SqlCommand command2 = new SqlCommand();                     command2.Connection = connection2;
                    command2.Connection = connection2;

 if (!chkGenError.Checked)
                    if (!chkGenError.Checked)  {
{ //Restore database to near it's original condition so sample will work correctly.
                        //Restore database to near it's original condition so sample will work correctly. command2.CommandText = "DELETE FROM stores WHERE (stor_id = '9797') OR (stor_id = '9798')";
                        command2.CommandText = "DELETE FROM stores WHERE (stor_id = '9797') OR (stor_id = '9798')"; command2.ExecuteNonQuery();
                        command2.ExecuteNonQuery(); }
                    }
 //Insert the first record.
                    //Insert the first record. command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')";
                    command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')"; command2.ExecuteNonQuery();
                    command2.ExecuteNonQuery();
 //Insert the second record.
                    //Insert the second record. command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')";
                    command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')"; command2.ExecuteNonQuery();
                    command2.ExecuteNonQuery();                     }
                } }
            } //  The Complete method commits the transaction.
            //  The Complete method commits the transaction.
 if (chkCommit.Checked)
            if (chkCommit.Checked)  {
{ transScope.Complete();
                transScope.Complete(); }
            } }
        } }
    }
 private void Test3()
    private void Test3()
 
     {
{ string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind";
        string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind"; string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs";
        string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs"; string dt = DateTime.Now.ToString();
        string dt = DateTime.Now.ToString();
 using (TransactionScope transScope = new TransactionScope())
        using (TransactionScope transScope = new TransactionScope())  {
{ using (SqlConnection connection1 = new
            using (SqlConnection connection1 = new
 SqlConnection(connectString1))
               SqlConnection(connectString1))  {
{ // Opening connection1 automatically enlists it in the
                // Opening connection1 automatically enlists it in the  // TransactionScope as a lightweight transaction.
                // TransactionScope as a lightweight transaction. connection1.Open();
                connection1.Open();
 // Do work in the first connection.
                // Do work in the first connection. SqlCommand command1 = new SqlCommand();
                SqlCommand command1 = new SqlCommand(); command1.Connection = connection1;
                command1.Connection = connection1;
 //Restore database to near it's original condition so sample will work correctly.
                //Restore database to near it's original condition so sample will work correctly. command1.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
                command1.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery();
 //Insert the first record.
                //Insert the first record. command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
                command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery();
 //Insert the second record.
                //Insert the second record. command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
                command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"; command1.ExecuteNonQuery();
                command1.ExecuteNonQuery(); }
            }
 // Assumes conditional logic in place where the second
            // Assumes conditional logic in place where the second // connection will only be opened as needed.
            // connection will only be opened as needed. using (SqlConnection connection2 = new
            using (SqlConnection connection2 = new
 SqlConnection(connectString2))
                SqlConnection(connectString2))  {
{ // Open the second connection, which enlists the
                // Open the second connection, which enlists the  // second connection and promotes the transaction to
                // second connection and promotes the transaction to // a full distributed transaction.
                // a full distributed transaction.  connection2.Open();
                connection2.Open();
 // Do work in the second connection.
                // Do work in the second connection. SqlCommand command2 = new SqlCommand();
                SqlCommand command2 = new SqlCommand(); command2.Connection = connection2;
                command2.Connection = connection2;

 if (!chkGenError.Checked)
                if (!chkGenError.Checked)  {
{ //Restore database to near it's original condition so sample will work correctly.
                    //Restore database to near it's original condition so sample will work correctly. command2.CommandText = "DELETE FROM stores WHERE (stor_id = '9797') OR (stor_id = '9798')";
                    command2.CommandText = "DELETE FROM stores WHERE (stor_id = '9797') OR (stor_id = '9798')"; command2.ExecuteNonQuery();
                    command2.ExecuteNonQuery(); }
                }
 //Insert the first record.
                //Insert the first record. command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')";
                command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')"; command2.ExecuteNonQuery();
                command2.ExecuteNonQuery();
 //Insert the second record.
                //Insert the second record. command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')";
                command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')"; command2.ExecuteNonQuery();
                command2.ExecuteNonQuery(); }
            }
 //  The Complete method commits the transaction.
            //  The Complete method commits the transaction.
 if (chkCommit.Checked)
            if (chkCommit.Checked)  {
{ transScope.Complete();
                transScope.Complete(); }
            } }
        } }
    }    
测试的时候需要在 Sql Server 服务管理器中开启 MSDTC,我是再 SQL 2k上做的测试,2k5上应该得到更好的支持。
方法 Test1() 是单数据库的事务,只是为了测试;
Test2()和Test3() 没有实质区别,都是自动注册事务。
完整代码下载:/Files/Jinglecat/DTCSQL.rar
同时我也尝试了一个Oracle版本,Oracle 10g 2,其中数据库orcl 是默认的启动数据库,而数据库nhrs是我自己建的一个数据库,测试通过,代码跟SQL没有两样,之前网上查到有网友说OracleClient还不支持 MSDTC,查了很多资料,确实在 ado.net 1.x 有问题,现在可以确信 ado.net 2.0 已经可以支持。
完整代码下载:/Files/Jinglecat/DTCORA.rar
现在银杏事务已经可以满数项目需求了,有时间再多更多的研究了^_^