项目涉及到多个数据库的查询更新操作,也就必然需要分布式事务的支持,查了MSDN知道 .net 2.0 中利用新增的 System.Transactions 命名空间可以简单的实现分布式事务:
System.Transactions 基础结构通过支持在 SQL Server、ADO.NET、MSMQ 和 Microsoft 分布式事务协调器 (MSDTC) 中启动的事务,使事务编程在整个平台上变得简单和高效。它提供基于 Transaction 类的显式编程模型,还提供使用 TransactionScope 类的隐式编程模型,在这种模型中事务是由基础结构自动管理的。强烈建议使用更为方便的隐式模型进行开发。
具体参考:http://msdn2.microsoft.com/zh-cn/library/system.transactions(VS.80).aspx 以及相关的连接都提供了非常详细的信息参考MSDN的Demo做了SQL Server 2000 的 使用事务范围实现隐式事务测试,事务可以正常提交以及回滚:
private void Test1()
{
// 使用事务范围实现隐式事务
// 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()) {
//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");
SqlCommand myCommand = new SqlCommand();
myConnection.Open();
myCommand.Connection = myConnection;
if (!chkGenError.Checked) {
//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.ExecuteNonQuery();
}
//Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();
//Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();
myConnection.Close();
if (chkCommit.Checked) {
ts.Complete();
}
/**/////Call complete on the TransactionScope or not based on input
//ConsoleKeyInfo c;
//while (true) {
// Console.Write("Complete the transaction scope? [Y|N] ");
// c = Console.ReadKey();
// Console.WriteLine();
// if ((c.KeyChar == 'Y') || (c.KeyChar == 'y')) {
// // Commit the transaction
// ts.Complete();
// break;
// }
// else if ((c.KeyChar == 'N') || (c.KeyChar == 'n')) {
// break;
// }
//}
}
}
private void Test2()
{
string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind";
string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs";
string dt = DateTime.Now.ToString();
using (TransactionScope transScope = new TransactionScope()) {
using (SqlConnection connection1 = new
SqlConnection(connectString1)) {
// Opening connection1 automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Do work in the first connection.
SqlCommand command1 = new SqlCommand();
command1.Connection = connection1;
//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.ExecuteNonQuery();
//Insert the first record.
command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
command1.ExecuteNonQuery();
//Insert the second record.
command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
command1.ExecuteNonQuery();
// Assumes conditional logic in place where the second
// connection will only be opened as needed.
using (SqlConnection connection2 = new
SqlConnection(connectString2)) {
// Open the second connection, which enlists the
// second connection and promotes the transaction to
// a full distributed transaction.
connection2.Open();
// Do work in the second connection.
SqlCommand command2 = new SqlCommand();
command2.Connection = connection2;
if (!chkGenError.Checked) {
//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.ExecuteNonQuery();
}
//Insert the first record.
command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')";
command2.ExecuteNonQuery();
//Insert the second record.
command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')";
command2.ExecuteNonQuery();
}
}
// The Complete method commits the transaction.
if (chkCommit.Checked) {
transScope.Complete();
}
}
}
private void Test3()
{
string connectString1 = "server=(local);Integrated Security=SSPI;database=northwind";
string connectString2 = "server=(local);Integrated Security=SSPI;database=pubs";
string dt = DateTime.Now.ToString();
using (TransactionScope transScope = new TransactionScope()) {
using (SqlConnection connection1 = new
SqlConnection(connectString1)) {
// Opening connection1 automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Do work in the first connection.
SqlCommand command1 = new SqlCommand();
command1.Connection = connection1;
//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.ExecuteNonQuery();
//Insert the first record.
command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
command1.ExecuteNonQuery();
//Insert the second record.
command1.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
command1.ExecuteNonQuery();
}
// Assumes conditional logic in place where the second
// connection will only be opened as needed.
using (SqlConnection connection2 = new
SqlConnection(connectString2)) {
// Open the second connection, which enlists the
// second connection and promotes the transaction to
// a full distributed transaction.
connection2.Open();
// Do work in the second connection.
SqlCommand command2 = new SqlCommand();
command2.Connection = connection2;
if (!chkGenError.Checked) {
//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.ExecuteNonQuery();
}
//Insert the first record.
command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9797', 'ebay')";
command2.ExecuteNonQuery();
//Insert the second record.
command2.CommandText = "Insert into stores (stor_id, stor_name) VALUES ('9798', 'amazon')";
command2.ExecuteNonQuery();
}
// The Complete method commits the transaction.
if (chkCommit.Checked) {
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
现在银杏事务已经可以满数项目需求了,有时间再多更多的研究了^_^