当应用程序需要在多个数据库中进行事务性操作的时候,使用TransactionScope类可以方便地实现应用程序的这一需求。只要对多个数据库的操作代码位于同一个事务范围内,即可实现多数据库连接的事务操作。
技术要点
本示例主要说明了如何在程序中使用TransactionScope实现多数据库连接事务操作,技术要点如下。
— 因为位于同一个事务范围内的不同的数据库操作,程序视为同一个事务,所以使用事务范围能够简便地实现多数据连接的事务操作。
— 在事务范围内应调用且仅仅调用一次Complete方法,当事务范围的Complete方法调用时,事务范围中的数据操作尝试提交,提交失败时自动回滚,如果在事务范围内未执行Complete方法,则导致事务范围在操作未提交的情况下结束。
实现步骤
(1)创建控制台应用程序项目,命名为“MultiDatabaseTransactionScope”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System;
using System.Collections.Generic;
using System.Text;
using System.Transactions;
using System.Data;
using System.Data.SqlClient;
namespace MultiDatabaseTransactionScope
{
class Program
{
static void Main(string[] args)
{
//在创建的事务范围实例内运行代码
using (TransactionScope ts = new TransactionScope())
{
//连接数据库1的字符串
string ConnectionString1 = @"Data Source = localhost; Initial Catalog = Northwind; Integrated Security = SSPI;";
//创建数据库1连接类实例1
SqlConnection conn1 = new SqlConnection(ConnectionString1);
//创建数据库1命令类实例1
SqlCommand command1 = new SqlCommand(@"INSERT Shippers(CompanyName,Phone)
VALUES('Test Ship2','0000-0002')", conn1);
conn1.Open();//连接数据库1
command1.ExecuteNonQuery();//在数据库1上执行命令
Console.WriteLine("数据库1的命令已执行");
conn1.Close();//关闭数据库1
//连接数据库2的字符串
string ConnectionString2 = @"Data Source = localhost; Initial Catalog = pubs; Integrated Security = SSPI;";
//创建数据库2连接类实例2
SqlConnection conn2 = new SqlConnection(ConnectionString2);
//创建数据库2命令类实例2
SqlCommand command2 = new SqlCommand(@"INSERT Discounts(Discounttype,Discount) VALUES('Other',12)", conn2);
conn2.Open();//连接数据库2
command2.ExecuteNonQuery();//在数据库2上执行命令
Console.WriteLine("数据库2的命令已执行");
conn2.Close();//关闭数据库2
Console.Write("是否提交事务?(Y/N)");
if (Console.ReadKey(false).Key == ConsoleKey.Y)
{
ts.Complete();//提交事务
Console.WriteLine("");
Console.WriteLine("事务提交完成");
}
else
{
Console.WriteLine("取消事务提交");
}
}
}
}
}
(3)按F5键运行程序,运行结果如下所示。
数据库1的命令已执行
数据库2的命令已执行
是否提交事务?(Y/N)y
事务提交完成
源程序解读
本示例程序创建了一个事务范围,并在该事务范围内创建两个不同的数据库连接,并通过SqlCommand类实例执行数据操作,最后通过用户的键盘输入决定是否提交两个数据库连接中的数据操作事务。
TransactionScope是.Net Framework 2.0滞后,新增了一个名称空间。它的用途是为数据库访问提供了一个“轻量级”[区别于:SqlTransaction]的事物。使用之前必须添加对 System.Transactions.dll 的引用。下列代码就是一个正在创建的事务,这个事务自身还封装了多个数据库查询。只要任意一个 SqlCommand 对象引发异常,程序流控制就会跳出 TransactionScope 的 using 语句块,随后,TransactionScope 将自行释放并回滚该事务。由于这段代码使用了 using 语句,所以 SqlConnection 对象和 TransactionScope 对象都将被自动调用Dispose()释放。由此可见,只需添加很少的几行代码,您就可以构建出一个事务模型,这个模型可以对异常进行处理,执行结束后会自行清理,此外,它还可以对命令的提交或回滚进行管理。
using (TransactionScope tsCope = new TransactionScope())
{
using (SqlConnection cn2005 = new SqlConnection(someSql2005))
{
SqlCommand cmd = new SqlCommand(sqlUpdate, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
}
using (SqlConnection cn2005 = new SqlConnection(anotherSql2005))
{
SqlCommand cmd = new SqlCommand(sqlDelete, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
}
tsCope.Complete();
}
连接字符串关键字(Enlist)
SqlConnection.ConnectionString 属性支持关键字 Enlist,该关键字指示 System.Data.SqlClient 是否将检测事务上下文并自动在分布式事务中登记连接。 如果 Enlist=true,连接将自动在打开的线程的当前事务上下文中登记。 如果 Enlist=false,SqlClient 连接不会与分布式事务进行交互。 Enlist 的默认值为 true。 如果连接字符串中未指定 Enlist,若在连接打开时检测到一个,连接将自动在分布式事务中登记。
上面所看到的示例中我们使用了TransactionScope的默认设置。TransactionScope有三种模式:
TransactionScopeOptions | 描述 |
Required | 如果已经存在一个事务,那么这个事务范围将加入已有的事务。否则,它将创建自己的事务。 |
RequiresNew | 这个事务范围将创建自己的事务。 |
Suppress | 如果处于当前活动事务范围内,那么这个事务范围既不会加入氛围事务 (ambient transaction),也不会创建自己的事务。当部分代码需要留在事务外部时,可以使用该选项。 |
您可以在代码的任何位置上随是查看是否存在事务范围,具体方法就是查看 System.Transactions.Transaction.Current 属性。如果这个属性为“null”,说明不存在当前事务。
若要更改 TransactionScope 类的默认设置,您可以创建一个 TransactionOptions 对象,然后通过它在 TransactionScope 对象上设置隔离级别和事务的超时时间。TransactionOptions 类有一个 IsolationLevel 属性,通过这个属性可以更改隔离级别,例如从默认的可序列化 (Serializable) 改为ReadCommitted,甚至可以改为 SQL Server 2005 引入的新的快照 (Snapshot) 级别。(请记住,隔离级别仅仅是一个建议。大多数数据库引擎会试着使用建议的隔离级别,但也可能选择其他级别。)此外,TransactionOptions 类还有一个 TimeOut 属性,这个属性可以用来更改超时时间(默认设置为 1 分钟)。
下列代码中使用了默认的 TransactionScope 对象及其默认构造函数。也就是说,它的隔离级别设置为可序列化 (Serializable),事务的超时时间为 1 分钟,而且 TransactionScopeOptions 的设置为 Required。
//设置TransactionOptions模式
tOpt.IsolationLevel = IsolationLevel.ReadCommitted;
// 设置超时间隔为2分钟,默认为60秒
tOpt.Timeout = new TimeSpan(0, 2, 0);
string cnString = ConfigurationManager.ConnectionStrings["sql2005DBServer"].ConnectionString);
using (TransactionScope tsCope = new TransactionScope(TransactionScopeOption.RequiresNew, tOpt))
{
using (SqlConnection cn2005 = new SqlConnection(cnString)
{
SqlCommand cmd = new SqlCommand(updateSql1, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
}
tsCope.Complete();
}
嵌套应用
如下列代码,假设 Method1 创建一个 TransactionScope,针对一个数据库执行一条命令,然后调用 Method2。Method2 创建一个自身的 TransactionScope,并针对一个数据库执行另一条命令。
{
using (TransactionScope ts =
new TransactionScope(TransactionScopeOption.Required))
{
using (SqlConnection cn2005 = new SqlConnection())
{
SqlCommand cmd = new SqlCommand(updateSql1, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
}
Method2();
ts.Complete();
}
}
private void Method2()
{
using (TransactionScope ts =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (SqlConnection cn2005 = new SqlConnection())
{
SqlCommand cmd = new SqlCommand(updateSql2, cn2005);
cn2005.Open();
cmd.ExecuteNonQuery();
}
ts.Complete();
}
}
总结:
进入和退出事务都要快,这一点非常重要,因为事务会锁定宝贵的资源。最佳实践要求我们在需要使用事务之前再去创建它,在需要对其执行命令前迅速打开连接,执行动作查询 (Action Query),并尽可能快地完成和释放事务。在事务执行期间,您还应该避免执行任何不必要的、与数据库无关的代码,这能够防止资源被毫无疑义地锁定过长的时间。
http://www.cnblogs.com/chenbg2001/archive/2010/04/22/1717920.html