第13章 事务处理
事务处理是包含一个或多个任务的一组关联操作的提交或回滚操作。在事务执行的过程中,保证事务具有基本的ACID属性(原子、一致性、隔离和持久性)。.NET Framework的事务管理支持多种事务处理方式,包括显性事务和隐性事务、本地事务和分布式事务、事务嵌套、事务升级等,同时包含了三种主要信任级别:AllowPartiallyTrustedCallers(APTCA)信任级别、DistributedTransactionPermission(DTP)信任级别和完全信任级别。要使用事务处理应在程序头部引用System.Transactions命名空间。
13.1 使用SqlTransaction实现数据库操作事务
SqlTransaction类是对SQL Server数据库进行事务处理的类,该类的实例由SqlConnection类实例的BeginTransaction方法创建,表示在该数据库连接实例上开始一个数据库事务,创建SqlTransaction类实例后,在程序中使用该实例的Commit方法提交事务,或者使用该类的Rollback方法回滚事务。
技术要点
本示例主要说明了如何在程序中使用SqlTransaction实现数据库操作事务,技术要点如下。
SqlTransaction类实例由SqlConnection类实例的BeginTransaction方法创建,表示将在该数据库连接实例上开始一个数据库操作事务。
数据库连接作为事务范围时,当事务范围结束数据库连接将会自动关闭。
创建操作数据库的SqlCommand类实例时,必须使用数据库连接和数据库事务来创建,该数据库命令实例才能在事务中登记,才能使该命令的执行纳入事务处理的范围内。
实现步骤
(1)创建控制台应用程序项目,命名为“DatabaseSqlTransaction”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.SqlClient; namespace DatabaseSqlTransaction { class Program { static void Main(string[] args) { //连接数据库的字符串 string ConnectionString = @"Data Source = localhost; Initial Catalog = Northwind; Integrated Security = SSPI;"; //使用数据库连接作为事务范围 using (SqlConnection conn = new SqlConnection(ConnectionString)) { conn.Open();//连接数据库 SqlTransaction transaction; //开始一个本地事务 transaction = conn.BeginTransaction("MyTransaction"); //必须为SqlCommand指定数据库连接和登记的事务 SqlCommand cmd = new SqlCommand("", conn, transaction); try { //向数据表中插入记录的命令语句 cmd.CommandText = @"Insert into Region (RegionID, RegionDescription) VALUES (10, '自定义描述A')"; cmd.ExecuteNonQuery(); cmd.CommandText = @"Insert into Region (RegionID, RegionDescription) VALUES (11, '自定义描述B')"; cmd.ExecuteNonQuery(); transaction.Commit();//提交事务 Console.WriteLine("操作完成"); } catch (Exception ex) { Console.WriteLine("提交错误类型: {0}", ex.GetType()); Console.WriteLine("提交错误信息: {0}", ex.Message); try { transaction.Rollback();//回滚事务 } catch (Exception ex2) { Console.WriteLine("回滚错误类型: {0}", ex2.GetType()); Console.WriteLine("回滚错误信息: {0}", ex2.Message); } } } Console.ReadLine(); } } }
|
(3)按F5键运行程序,运行结果如下所示。
(4)按下回车键结束程序运行,再次按下F5键运行程序,运行结果如下所示。
提交错误类型: System.Data.SqlClient.SqlException 提交错误信息: 违反了 PRIMARY KEY 约束 'PK_Region'。不能在对象 'Region' 中插入重 复键。 语句已终止。
|
源程序解读
(1)本示例程序创建一个SqlConnection类的实例,并将该实例作为事务范围,在该范围内使用SqlConnection类实例创建一个SqlTransaction类实例的本地事务,使用数据库连接类实例和数据库事务类实例,创建一个SqlCommand类的实例,并为该SqlCommand类实例指定数据库操作命令,执行该操作如果执行过程正常,则提交事务,否则,将数据库操作事务回滚。本示例程序的流程图如图13.1所示。
|
图13.1 使用SqlTransaction实现数 据库操作事务示例程序流程图 |
(2)在本示例中使用了信任连接字符串连接SQL Server数据库,在数据库连接包含的事务范围结束时会自动关闭数据库连接,不需要编写关闭数据库连接的代码。
13.2 使用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成员创建的事务范围内的数据操作。
13.3 使用TransactionScope实现单数据库连接事务操作
TransactionScope类表示一个事务范围,位于该范围内的代码块都成为一个事务,在调用TransactionScope类中定义的Complete方法时将结束并提交事务。
技术要点
本示例主要说明了如何在程序中使用TransactionScope实现单数据库连接的事务操作,技术要点如下。
在TransactionScope类实例表示的事务范围中,所有的数据库操作代码都将作为一个事务执行,只有在调用该事务范围实例的Complete方法时,才提交对数据库的操作。
当事务范围中的数据库操作失败时,事务范围自动回滚数据库操作。
实现步骤
(1)创建控制台应用程序项目,命名为“SingleDatabaseTransactionScope”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Transactions; using System.Data; using System.Data.SqlClient; namespace SingleDatabaseTransactionScope { class Program { static void Main(string[] args) { //在创建的事务范围实例内运行代码 using (TransactionScope ts = new TransactionScope()) { //连接数据库的字符串 string ConnectionString = @"Data Source = localhost; Initial Catalog = Northwind; Integrated Security = SSPI;"; //创建数据库连接类实例 SqlConnection conn = new SqlConnection(ConnectionString); //创建三个数据库SqlCommand命令类实例 SqlCommand command1 = new SqlCommand (@"DELETE FROM Shippers WHERE CompanyName LIKE 'Test Ship%'", conn); SqlCommand command2 = new SqlCommand (@"INSERT Shippers(CompanyName,Phone) VALUES('Test Ship1','0000-0001')", conn); SqlCommand command3 = new SqlCommand (@"INSERT Shippers(CompanyName,Phone) VALUES('Test Ship2','0000-0002')", conn); try { conn.Open();//打开数据库 command1.ExecuteNonQuery();//执行查询 Console.WriteLine("第一个命令执行成功"); command2.ExecuteNonQuery(); Console.WriteLine("第二个命令执行成功"); command3.ExecuteNonQuery(); Console.WriteLine("第三个命令执行成功"); } finally { if (conn.State == ConnectionState.Open) { conn.Close();//关闭数据库 } } Console.Write("是否提交事务?(Y/N)"); if (Console.ReadKey(false).Key== ConsoleKey.Y) { ts.Complete();//结束事务,提交数据库执行的命令 Console.WriteLine(""); Console.WriteLine("提交事务完毕"); } else { Console.WriteLine(""); Console.WriteLine("取消事务提交"); } Console.ReadLine(); } } } } |
(3)按F5键运行程序,运行结果如下所示。
第一个命令执行成功 第二个命令执行成功 第三个命令执行成功 是否提交事务?(Y/N)y 提交事务完毕 |
源程序解读
(1)本示例程序创建一个事务范围,在该事务范围内创建数据库连接,并在该数据库连接上创建三个SqlCommand类的实例,执行这三个数据库命令,最后根据用户通过键盘输入决定是否提交事务。本示例程序的流程图如图13.3所示。
|
图13.3 使用事务范围实现单数据 库连接事务操作的示例程序流程图 |
(2)使用TransactionScope类示例创建事务范围实现事务处理的方式,称为隐式事务编程模型。在该模型中,事务的基本的ACID属性(原子、一致性、隔离和持久性),由该类的基础结构实现、事务的提交和回滚实际上都在基础结构中完成,该类中定义的Complete方法实际上仅仅是通知基础结构当前事务代码已经完成可以开始提交。
13.4 使用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 事务提交完成 |
源程序解读
本示例程序创建了一个事务范围,并在该事务范围内创建两个不同的数据库连接,并通过SqlCommand类实例执行数据操作,最后通过用户的键盘输入决定是否提交两个数据库连接中的数据操作事务。本示例程序的流程图如图13.4所示。
|
图13.4 使用事务范围实现多数据 库连接事务操作的示例程序流程图 |