使用TransactionScope实现单数据库连接事务操作

当应用程序需要在多个数据库中进行事务性操作的时候,使用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

事务提交完成

 

 使用2.0的新事务方式也有快一年了,刚开始时候遇到的一些使用疑点问题都在现在的项目中遇到,并解决,现在做一下总结:

      一、 在TransactionScope中,如果不是必须要避免它启用DTC分布式事务,因为性能低下;而对于TransactionScope来说它是以连 接对象Connection做为识别单位的,也就是说即便是相同连接字符串ConnectionString的两个连接对象Connection在 TransactionScope也是会启用DTC分布式事务的,避免的方法就是在一个TransactionScope中使用一个唯一的连接对象 Connection。


      二、在TransactionScope中默认的事务级别是Serializable,即在 事务过程中,完全性锁表。别的进程不能查询,修改,新增,删除。这样会导致效率大大降低,虽然数据完整性很高。通常我们不需要那么高的数据完整性。所以需 要修改默认的事务级别:

                TransactionOptions option = new TransactionOptions();
                option.IsolationLevel 
= System.Transactions.IsolationLevel.ReadCommitted;
                
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, option))

      所有的事务级别如下:
成员名称 说明 
 Chaos                         无法改写隔离级别更高的事务中的挂起的更改。  
 ReadCommitted       不可以在事务期间读取可变数据,但是可以修改它。  
 ReadUncommitted    可以在事务期间读取和修改可变数据。  
 RepeatableRead       可以在事务期间读取可变数据,但是不可以修改。可以在事务期间添加新数据。  
 Serializable                可以在事务期间读取可变数据,但是不可以修改,也不可以添加任何新数据。  
 Snapshot                   可以读取可变数据。在事务修改数据之前,它验证在它最初读取数据之后另一个事务是否更改过这些数据。如果数据已被更新,则会引发错误。这样使事务可获取先前提交的数据值。 
在 尝试提升以此隔离级别创建的事务时,将引发一个 InvalidOperationException,并产生错误信息 “Transactions with IsolationLevel Snapshot cannot be promoted”(无法提升具 有 IsolationLevel 快照的事务)。
 
 Unspecified                正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。如果设置了此值,则会引发异常。

 

 使用TransactionScopeOption实现事务选项控制

TransactionScopeOption枚举是创建事务范围的重要选项,该枚举中包含三个成员,其中Required成员表示创建的范围需要 一个事务,如果已经存在外部的事务,就使用外部事务作为当前的事务,如果不存在外部事务,就创建一个新的事务,在事务范围的构造函数中,如果没有指定 TransactionScopeOption枚举的参数,默认使用Required创建事务范围。RequiresNew成员表示为当前范围创建一个新 的事务,而无论是否存在外部事务。Suppress成员表示取消当前范围的外部事务,范围内的所有操作在事务外部执行。

技术要点

本示例主要说明了如何在程序中使用TransactionScopeOption实现事务选项控制,技术要点如下。
 
使用RequiresNew成员创建的事务范围时,即使存在外部事务,当前范围也将创建新的事务,这样当前事务范围在调用Complete方法结束事务范围的时候,就执行了提交动作,而不是等到外部事务调用Complete方法时才进行提交。
 
使用Required成员创建的事务范围时,如果存在外部事务,就使用外部事务,这样当前事务范围在调用Complete方法结束事务范围的时候,实际上并不能进行执行提交动作。只有当外部的事务范围调用Complete方法结束时,才能执行提交动作。

实现步骤

(1)创建控制台应用程序项目,命名为“ControlTransactionScopeOption”。

(2)打开并编辑Program.cs文件,代码如下所示。

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;
namespace ControlTransactionScopeOption
{
class Program
{
static void Main(string[] args)
{
//使用事务范围ts1
using (TransactionScope ts1 = new TransactionScope())
{
//连接数据库的字符串1
string ConnectionString1 = @"Data Source = localhost;
Initial Catalog = Northwind; Integrated Security = SSPI;";
//使用RequiresNew参数创建ts1的子事务subts1
using (TransactionScope subts1 = new TransactionScope
(TransactionScopeOption.RequiresNew))
{
//创建数据库连接类实例1
SqlConnection conn1 = new SqlConnection(ConnectionString1);
//将在数据库连接实例1上执行的命令
SqlCommand command1 = new SqlCommand(@"INSERT Shippers
(CompanyName, Phone) VALUES('Test Ship1','0000-0001')", conn1);
conn1.Open();
command1.ExecuteNonQuery();//执行数据库操作
conn1.Close();
subts1.Complete();//完成子事务1
}
//连接数据库的字符串2
string ConnectionString2 = @"Data Source = localhost;
Initial Catalog = Northwind; Integrated Security = SSPI;";
//使用Required参数创建ts1的子事务subts2
using (TransactionScope subts2 =new TransactionScope
(TransactionScopeOption.Required))
{
//创建数据库连接类实例2
SqlConnection conn2 = new SqlConnection(ConnectionString2);
//将在数据库连接实例2上执行的命令
SqlCommand command = new SqlCommand(@"INSERT Shippers
(CompanyName,Phone) VALUES('Test Ship2','0000-0002')",conn2);
conn2.Open();
command.ExecuteNonQuery();//执行数据库操作
conn2.Close();
subts2.Complete();//完成子事务
}
//完成ts1事务的语句,去掉注释后subts2的事务才能提交
//ts1.Complete();
//连接数据库的字符串3
string ConnectionString3 = @"Data Source = localhost;
Initial Catalog = Northwind; Integrated Security = SSPI;";
//创建数据库连接类实例3
SqlConnection conn3 = new SqlConnection(ConnectionString3);
//打开数据库连接
conn3.Open();
//读取数据库中Shippers表的数据
SqlDataReader dr = new SqlCommand("SELECT *
FROM Shippers", conn3).ExecuteReader();
while (dr.Read()) //循环显示数据
{
Console.WriteLine("{0} {1} {2}",
dr.GetInt32(0).ToString(),
dr.GetString(1),
dr.GetString(2));
}
dr.Close();//关闭SqlDataReader类实例
conn3.Close();//关闭数据库连接类实例
Console.ReadLine();
}
}
}
}

(3)按F5键运行程序,运行结果如下所示。
1 Speedy Express (503) 555-9831
2 United Package (503) 555-3199
3 Federal Shipping (503) 555-9931
4 Test Ship1 0000-0001

源程序解读

(1)本示例程序定义了一个外部的事务范围ts1,在该范围内分别使用TransactionScopeOption枚举的RequiresNew 成员和Required成员创建了两个事务范围。在这两个事务范围内,分别创建数据库连接,并执行SQL命令语句。然后在事务范围之外,查询并显示数据库 中的表记录,以检查事务的提交情况。本示例程序的流程图如图13.2所示。

  
图13.2  使用TransactionScopeOption实现事务选项控制的示例程序流程图


(2)根据程序运行结果显示,使用Required成员创建的事务范围中的操作未被提交,原因是Required成员创建的事务范围使用的是外部事务,在外部事务未提交时当前事务范围中的所有数据操作均未提交。

(3)除去本示例程序注释的外部事务范围ts1的调用Complete方法语句,将结束ts1事务,并将该事务范围内的所有数据操作提交,此时将提交Required成员创建的事务范围内的数据操作。

 

 对MSDTC组件设置:
 步骤:
  在控制面板--->管理工具--->服务 中,开启Distributed Transaction Coordinator 服务。
 a.控制面板->管理工具->组件服务->计算机->我的电脑->右键->属性
 b.选择MSDTC页, 确认"使用本地协调器"
 c.点击下方"安全配置"按钮
 d.勾选: "允许网络DTC访问","允许远程客户端","允许入站","允许出站","不要求进行身份验证".
 e.对于数据库服务器端, 可选择"要求对呼叫方验证"
 f.勾选:"启用事务Internet协议(TIP)事务"。
 g.在双方防火墙中增加MSDTC.exe例外
   可用命令行: netsh firewall set allowedprogram %windir%\system32\msdtc.exe MSDTC enable

4、重启IIS服务器。
[转自:http://www.cnblogs.com/haoliansheng/archive/2009/03/12/1409286.html]

posted @ 2009-08-18 11:15  赖文华.NET  阅读(1333)  评论(0编辑  收藏  举报