首页 > 开发 > 综合 > 正文

C#数据库事务原理及实践(上)

2024-07-21 02:26:34
字体:
来源:转载
供稿:网友

本文将涉及到在.net框架下使用c#语言操纵数据库事务的各个方面。


体验sql语言的事务机制

作为大型的企业级数据库,sql server2000对事务提供了很好的支持。我们可以使用sql语句来定义、提交以及回滚一个事务。

如下所示的sql代码定义了一个事务,并且命名为"mytransaction"(限于篇幅,本文并不讨论如何编写sql语言程序,请读者自行参考相关书籍):

declare @tranname varchar(20)

select @tranname = 'mytransaction'
begin transaction @trannamegouse pubs
go

update roysched
set royalty = royalty * 1.10
where title_id like 'pc%'
go

commit transaction mytransaction
go
 


这里用到了sql server2000自带的示例数据库pubs,提交事务后,将为所有畅销计算机书籍支付的版税增加 10%。

打开sql server2000的查询分析器,选择pubs数据库,然后运行这段程序,结果显而易见。

可是如何在c#程序中运行呢?我们记得在普通的sql查询中,一般需要把查询语句赋值给salcommand.commandtext属性,这里也就像普通的sql查询语句一样,将这些语句赋给sqlcommand.commandtext属性即可。要注意的一点是,其中的"go"语句标志着sql批处理的结束,编写sql脚本是需要的,但是在这里是不必要的。我们可以编写如下的程序来验证这个想法:

//transql.csusing system;
using system.data;
using system.data.sqlclient;
namespace aspcn
{
 public class dbtransql
 {
  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) ";
    mycomm.commandtext+="select @tranname = 'mytransaction' ";
    mycomm.commandtext+="begin transaction @tranname ";
    mycomm.commandtext+="use pubs ";
    mycomm.commandtext+="update roysched set royalty = royalty * 1.10 where title_id like 'pc%' ";
    mycomm.commandtext+="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;password=";
   sqlconnection myconn=new sqlconnection(strsql);
   return myconn;
  }
 }

 public class test
 {
  public static void main()
  {
   dbtransql trantest=new dbtransql();
   trantest.dotran();
   console.writeline("事务处理已经成功完成。");
   console.readline();
  }
 }
}
 

 

  注意到其中的sqlcommand对象mycomm,它的commandtext属性仅仅是前面sql代码字符串连接起来即可,当然,其中的"go"语句已经全部去掉了。这个语句就像普通的查询一样,程序将sql文本事实上提交给dbms去处理了,然后接收返回的结果(如果有结果返回的话)。

很自然,我们最后看到了输出"事务处理已经成功完成",再用企业管理器查看pubs数据库的roysched表,所有title_id字段以"pc"开头的书籍的royalty字段的值都增加了0.1倍。

这里,我们并没有使用ado.net的事务处理机制,而是简单地将执行事务的sql语句当作普通的查询来执行,因此,事实上该事务完全没有用到.net的相关特性。
了解.net中的事务机制

如你所知,在.net框架中主要有两个命名空间(namespace)用于应用程序同数据库系统的交互:system.data.sqlclient和system.data.oledb。前者专门用于连接microsoft公司自己的sql server数据库,而后者可以适应多种不同的数据库。这两个命名空间中都包含有专门用于管理数据库事务的类,分别是system.data.sqlclient.sqltranscation类和system.data.oledb.oledbtranscation类。

就像它们的名字一样,这两个类大部分功能是一样的,二者之间的主要差别在于它们的连接机制,前者提供一组直接调用 sql server 的对象,而后者使用本机 ole db 启用数据访问。 事实上,ado.net 事务完全在数据库的内部处理,且不受 microsoft 分布式事务处理协调器 (dtc) 或任何其他事务性机制的支持。本文将主要介绍system.data.sqlclient.sqltranscation类,下面的段落中,除了特别注明,都将使用system.data.sqlclient.sqltranscation类。

事务的开启和提交

现在我们对事务的概念和原理都了然于心了,并且作为已经有一些基础的c#开发者,我们已经熟知编写数据库交互程序的一些要点,即使用sqlconnection类的对象的open()方法建立与数据库服务器的连接,然后将该连接赋给sqlcommand对象的connection属性,将欲执行的sql语句赋给它的commandtext属性,于是就可以通过sqlcommand对象进行数据库操作了。对于我们将要编写的事务处理程序,当然还需要定义一个sqltransaction类型的对象。并且看到sqlcommand对象的transcation属性,我们很容易想到新建的sqltransaction对象应该与它关联起来。

基于以上认识,下面我们就开始动手写我们的第一个事务处理程序。我们可以很熟练地写出下面这一段程序:

//dotran.csusing system;
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;password=";
   sqlconnection myconn=new sqlconnection(strsql);
   return myconn;
  }
 }

 public class test{public static void main()
 {
  dbtran trantest=new dbtran();
  trantest.dotran();
  console.writeline("事务处理已经成功完成。");
  console.readline();
 }
}
}
 


显然,这个程序非常简单,我们非常自信地编译它,但是,出乎意料的结果使我们的成就感顿时烟消云散:

error cs1501: 重载"sqltransaction"方法未获取"0"参数

是什么原因呢?注意到我们初始化的代码:

sqltransaction mytran=new sqltransaction();
 


显然,问题出在这里,事实上,sqltransaction类并没有公共的构造函数,我们不能这样新建一个sqltrancaction类型的变量。在事务处理之前确实需要有一个sqltransaction类型的变量,将该变量关联到sqlcommand类的transcation属性也是必要的,但是初始化方法却比较特别一点。在初始化sqltransaction类时,你需要使用sqlconnection类的begintranscation()方法:

sqltransaction mytran; mytran=myconn.begintransaction();
 

  
该方法返回一个sqltransaction类型的变量。在调用begintransaction()方法以后,所有基于该数据连接对象的sql语句执行动作都将被认为是事务mytran的一部分。同时,你也可以在该方法的参数中指定事务隔离级别和事务名称,如:

sqltransaction mytran;
mytran=myconn.begintransaction(isolationlevel.readcommitted,"sampletransaction");
 

  
关于隔离级别的概念我们将在随后的内容中探讨,在这里我们只需牢记一个事务是如何被启动,并且关联到特定的数据链接的。

先不要急着去搞懂我们的事务都干了些什么,看到这一行:

mytran.commit();
 


是的,这就是事务的提交方式。该语句执行后,事务的所有数据库操作将生效,并且为数据库事务的持久性机制所保持--即使系统在这以后发生致命错误,该事务对数据库的影响也不会消失。

对上面的程序做了修改之后我们可以得到如下代码(为了节约篇幅,重复之处已省略,请参照前文):

//dotran.cs……}

file://执行事务处理
public void dotran()
{
 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();
  }
}
……
 


到此为止,我们仅仅掌握了如何开始和提交事务。下一步我们必须考虑的是在事务中可以干什么和不可以干什么。

 

菜鸟学堂:
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表