C#数据库事务原理及实践
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作: · 更新客户所购商品的库存信息 · 保存客户付款信息--可能包括与银行系统的交互 · 生成订单并且保存到数据库中 · 更新用户相关信息,例如购物数量等等 正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。 数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。 2、事务ACID特性 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用 · 事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。 · 事务在完成时,必须使所有的数据都保持一致状态 · 由并发事务所作的修改必须与任何其它并发事务所作的修改隔离 · 事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。 DBMS的责任和我们的任务 企业级的数据库管理系统(DBMS)都有责任提供一种保证事务的物理完整性的机制。就常用的SQL Server2000系统而言,它具备锁定设备隔离事务、记录设备保证事务持久性等机制。因此,我们不必关心数据库事务的物理完整性,而应该关注在什么情况下使用数据库事务、事务对性能的影响,如何使用事务等等。 本文将涉及到在.net框架下使用C#语言操纵数据库事务的各个方面。 2、 作为大型的企业级数据库,SQL Server2000对事务提供了很好的支持。我们可以使用SQL语句来定义、提交以及回滚一个事务。 如下所示的SQL代码定义了一个事务,并且命名为"MyTransaction"(限于篇幅,本文并不讨论如何编写SQL语言 DECLARE @TranName VARCHAR(20) SELECT @TranName = 'MyTransaction' BEGIN TRANSACTION @TranName GO USE pubs GO UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' GO COMMIT TRANSACTION MyTransaction GO 这里用到了SQL Server2000自带的示例数据库pubs,提交事务后,将为所有畅销计算机书籍支付的版税增加 10%。 可是如何在C#程序中运行呢?我们记得在普通的SQL查询中,一般需要把查询语句赋值给SalCommand.CommandText属性,这里也就像普通的SQL查询语句一样,将这些语句赋给SqlCommand.CommandText属性即可。要注意的一点是,其中的"GO"语句标志着SQL批处理的结束,编写SQL脚本是需要的,但是在这里是不必要的。我们可以编写如下的程序来验证这个想法: using System.Data; using System.Data.SqlClient; namespace Aspcn { { file://将事务放到SQL Server中执行 public void DoTran() { file://建立连接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); try { myComm.Connection=myConn; myComm.CommandText="DECLARE @TranName VARCHAR(20) "+ "SELECT @TranName = 'MyTransaction' "+ "BEGIN TRANSACTION @TranName "+ "USE pubs "+ "UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' "+ "COMMIT TRANSACTION MyTransaction "; myComm.ExecuteNonQuery(); } catch(Exception err) { throw new ApplicationException("事务操作出错,系统信息:"+err.Message); } finally { myConn.Close(); } } file://获取数据连接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;事务处理已经成功完成。"); Console.ReadLine(); } } } 也就是执行一段SQL命令。 注意到其中的SqlCommand对象myComm,它的CommandText属性仅仅是前面SQL代码字符串连接起来即可,当然,其中的"GO"语句已经全部去掉了。这个语句就像普通的查询一样,程序将SQL文本事实上提交给DBMS去处理了,然后接收返回的结果(如果有结果返回的话)。 很自然,我们最后看到了输出"事务处理已经成功完成",再用企业管理器查看pubs数据库的roysched表,所有title_id字段以"PC"开头的书籍的royalty字段的值都增加了0.1倍。 这里,我们并没有使用ADO.net的事务处理机制,而是简单地将执行事务的SQL语句当作普通的查询来执行,因此,事实上该事务完全没有用到.net的相关特性 就像它们的名字一样,这两个类大部分功能是一样的,二者之间的主要差别在于它们的连接机制,前者提供一组直接调用 SQL Server 的对象,而后者使用本机 OLE DB 启用数据访问。 事实上,ADO.net 事务完全在数据库的内部处理,且不受 Microsoft 分布式事务处理协调器 (DTC) 或任何其他事务性机制的支持。本文将主要介绍System.Data.SqlClient.SqlTranscation类,下面的段落中,除了特别注明,都将使用System.Data.SqlClient.SqlTranscation类。 4、事务的开启和提交 using System.Data; using System.Data.SqlClient; namespace Aspcn { public class DbTran { file://执行事务处理 public void DoTran() { file://建立连接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); SqlTransaction myTran=new SqlTransaction(); try { myComm.Connection=myConn; myComm.Transaction=myTran; file://定位到pubs数据库 myComm.CommandText="USE pubs"; myComm.ExecuteNonQuery(); file://更新数据 file://将所有的计算机类图书 myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery();//提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException("事务操作出错,系统信息:"+err.Message); } finally { myConn.Close(); } } file://获取数据连接 private SqlConnection GetConn() { string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;事务处理已经成功完成。"); Console.ReadLine(); } } } error CS1501: 重载"SqlTransaction"方法未获取"0"参数 是什么原因呢?注意到我们初始化的代码: 该方法返回一个SqlTransaction类型的变量。在调用BeginTransaction()方法以后,所有基于该数据连接对象的SQL语句执行动作都将被认为是事务MyTran的一部分。同时,你也可以在该方法的参数中指定事务隔离级别和事务名称,如: myTran=myConn.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction"); 关于隔离级别的概念我们将在随后的内容中探讨,在这里我们只需牢记一个事务是如何被启动,并且关联到特定的数据链接的。 先不要急着去搞懂我们的事务都干了些什么,看到这一行: 对上面的程序做了修改之后我们可以得到如下代码(为了节约篇幅,重复之处已省略,请参照前文): file://执行事务处理 { file://建立连接并打开 SqlConnection myConn=GetConn(); myConn.Open(); SqlCommand myComm=new SqlCommand(); file://SqlTransaction myTran=new SqlTransaction(); file://注意,SqlTransaction类无公开的构造函数 SqlTransaction myTran; file://创建一个事务 myTran=myConn.BeginTransaction(); try { file://从此开始,基于该连接的数据操作都被认为是事务的一部分 file://下面绑定连接和事务对象 myComm.Connection=myConn; myComm.Transaction=myTran; file://定位到pubs数据库 myComm.CommandText="USE pubs"; myComm.ExecuteNonQuery();//更新数据 file://将所有的计算机类图书 myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'"; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException("事务操作出错,系统信息:"+err.Message); } finally { myConn.Close(); } } …… |
C#数据库事务原理及实践
最新推荐文章于 2024-11-17 23:07:03 发布
本文深入探讨了C#中如何使用SQL Server进行数据库事务处理,包括事务的开始、提交、回滚以及异常处理。通过实例展示了在进行多条SQL操作时确保数据一致性的重要性。
摘要由CSDN通过智能技术生成