.NET简谈事务、分布式事务处理

在本人的 .NET简谈事务本质论一文中我们从整体上了解了事务模型,在我们脑子里能有一个全局的事务处理结构,消除对数据库事务的依赖理解,重新认识事务编程模型。

今天这篇文章我们将使用.NET C#来进行事务性编程,从浅显、简单的本地事务开始,也就是我们用的最多的ADO.NET事务处理,然后我们逐渐扩大事务处理范围,包括对分布式事务处理的使用,多线程事务处理的使用。

 

数据库事务处理

数据库事务处理我们基本都很熟悉了,begin Transaction ……end Transaction,将要进行事务性的操作包在代码段里,为了便于文章有条理的讲解下去,我还是在这里穿插一个简单的小示例,便于与后面的代码进行对比分析。

1

我们在数据库里建两张表,也就是很简单一列信息。

表1名:test

表2名:test2

目的是为了掩饰事务的特性,所以我们这里给表1test的name列设置为主键,我们后面将通过有意的造成主键重复,导致事务自动回滚的效果。

我先来解释一下这两张表后面干什么用的。表test是用来有意造成事务内部处理出错用的,表test2是用来在事务处理当中扮演着没有错误的常规数据插入用的,我会在test2中先插入数据,然后在test中插入数据时触发事务内部执行错误导致事务回滚。

好了我们进行T-SQL的编写:

 

insert into test values('222') --我们在表test中插入一条记录
go
begin transaction tr
begin try
begin
insert into test2 values('111')
insert into test values('222') --该行插入会导致主键冲突,也就是我们要的效果
end
commit transaction tr
end try
begin catch
print '事务执行错误!'
print error_number()
rollback transaction tr
end catch

 

我们运行看看结果:

在事务处理过程中,很明显第一条插入语句执行成功了,但是由于第二条插入语句导致事务回滚所以数据是没有变化的。

这个示例可能过于简单,真正的企业级应用可能很复杂,但是我们的目的是看看事务的使用,越简单越明了越好。[王清培版权所有,转载请给出署名]

 

ADO.NET事务处理

下面我们将事务在.NET的AOD.NET中实现看看效果。

例2:

public class Test
    {
        SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        public void Add()
        {
            conn.Open();
            SqlCommand command = new SqlCommand("insert into test2 values(111)", conn);
            try
            {
                command.Transaction = conn.BeginTransaction();
                command.ExecuteNonQuery();
                command.CommandText = "insert into test values(222)";
                command.ExecuteNonQuery();
                command.Transaction.Commit();
            }
            catch (Exception err)
            {
                Console.WriteLine(err);
                command.Transaction.Rollback();
            }
        }
    }

 

 这就是典型ADO.NET事务处理代码,其实和我们第一个例子中的T-SQL代码是差不多的,通过ADO.NET中的SqlConnection.BeginTransaction()获取到对底层ODBC中的数据库事务的引用,其实这里还没有真正的设计到.NET中的事务处理代码,这里只是对数据库管理系统的远程调用,通过远程处理的消息通讯进行事务处理远程化。

事务信息显示类,为了便于观察事务的状态信息。

 

public class DisplayTransactioninfo
    {
        public static void Display(System.Transactions.Transaction tr)
        {
            if (tr != null)
            {
                Console.WriteLine("Createtime:" + tr.TransactionInformation.CreationTime);
                Console.WriteLine("Status:" + tr.TransactionInformation.Status);
                Console.WriteLine("Local ID:" + tr.TransactionInformation.LocalIdentifier);
                Console.WriteLine("Distributed ID:" + tr.TransactionInformation.DistributedIdentifier);
                Console.WriteLine();
            }
        }

 

 CommittableTransaction事务处理

从这里开始我们将接触到.NET中的事务处理,将了解到.NET中事务是怎样影响到远程数据库管理系统的事务处理的。

其实事务处理是一个非常复杂的技术领域,需要考虑很多可逆的技术实现,我们只是简单的了解原理和掌握基本的运用。

在我的 .NET简谈事务本质论一文中说到了事务的传递原理,那么事务传递意味着什么。其实事务传递的大概意思是将事务的执行范围通过网络传输的方式进行扩大到其他的机器上,比如我们在.NET中执行一项关于事务性的操作,那么在这个操作里面我们包含了对数据库的操作,这个时候对数据库的一系列操作都应该是属于事务范围内的,当事务回滚时还应该将数据库中的数据进行回滚才对。但是我们不可能总是显示的执行ADO.NET中的BeginTransaction,对于本地事务处理也就是单一资源管理器来说这也可以接受,那么如果在事务范围内涉及到多个资源管理器的操作,这就是分布式事务处理的范围了。所以说事务处理需要跨越网络传输形成无缝的面向服务的事务处理,数据库管理系统即有可能扮演者事务管理器的角色也有可能扮演着资源管理器的角色。太多的理论知识我这里就不多扯了,我们还是来看代码吧。

接着上面的实例,我们现在通过.NET中的事务处理来进行自动化的数据库事务处理。让数据库能自动的感知到我们正在进行事务性的操作。

3

我们利用Transaction类的子类CommittableTransaction可提交事务类来进行事务编程。

 

public class Test3
    {
        SqlConnection conn;
        CommittableTransaction committran = new CommittableTransaction();
        public Test3()
        {
            conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
            DisplayTransactioninfo.Display(committran);
        }
        public void Add3()
        {
            conn.Open();
            conn.EnlistTransaction(committran);//需要将本次的连接操作视为事务性的
            SqlCommand command = new SqlCommand();
            try
            {
                command.Connection = conn;
                command.CommandText = "insert into test2 values(111)";
                command.ExecuteNonQuery();
                command.CommandText = "insert into test values(222)";
                command.ExecuteNonQuery();
                committran.Commit();
            }
            catch (Exception err) { committran.Rollback(); //出现出错执行回滚操作}
        }
    }

 

 

数据源连接对象代表着远程数据库资源,所以在执行操作之前我们需要将资源管理器添加到本地事务管理器中进行后期的投票、提交管理。

 

EnterpriseService(COM+)自动化事务处理

在.NET2.0中有一个程序集不是太被人重视,System.EnterpriseServices.dll,这个程序集是.NET中为了使用早起的COM+技术的托管程序集,我们可以使用这个程序集来编写一些我们以前所不能编写的COM+应用程序。至于COM+应用程序的介绍网上一大堆,随便搜搜就有好多资料了,我印象中有一篇是潘爱明潘老师写的一个文章蛮好的,就是介绍COM+的所有特性。

我们继续来事务处理,下面我们看看怎么借用System.EnterpriseServices.Transaction类来进行自动化的事务处理。

4

 

[System.Runtime.InteropServices.ComVisible(true)]
//COM+是在COM的基础上发展起来的,需要将.NET程序集中的类型公开为COM组件。
    [System.EnterpriseServices.Transaction(TransactionOption.Required)]//始终需要事务处理域
    public class Test2 : ServicedComponent
    {
        public Test2() { }
        SqlConnection conn = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        [AutoComplete(true)]
//如果在方法的执行体类没有出现错误,那么将自动设置事务处理的结果
        public void Add2()
        {
            conn.Open();
            SqlCommand command = new SqlCommand();
            try
            {
                command.Connection = conn;
                command.CommandText = "insert into test2 values(111)";
                command.ExecuteNonQuery();
                command.CommandText = "insert into test values(222)";
                command.ExecuteNonQuery();
            }
            catch { System.EnterpriseServices.ContextUtil.SetAbort(); }
        }
    }

 

 DependentTransaction跨线程事务处理

我们在编写高并发量程序时,都会用到多线程来进行处理,让主线程能有时间来处理第一线的请求,然后将请求分发到各个子线程上进行后台的处理。我们来看一幅图:

 

我们假设上面这幅图是我们系统的一个内部结构,主线程主要的任务就是接受外来的请求,然后将具体的任务完成放到一个到两个子线程中去完成,但是子线程与子线程之间没有必然的关系,由于事务的上下文是不夸线程的,那么怎么将两个或者更多的线程串在一个事务里。[王清培版权所有,转载请给出署名]

我们来看看依赖事务处理,看代码:

5

 

public class Test6
    {
        CommittableTransaction commit = new CommittableTransaction();
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        public Test6()
        {
            conn1.Open();
            conn1.EnlistTransaction(commit);
        }
        public void Add6()
        {
            try
            {
                DisplayTransactioninfo.Display(commit);
                SqlCommand command = new SqlCommand("insert into test2 values(111)", conn1);
                command.ExecuteNonQuery();
                Thread thread = new Thread(Test6.CommitThread);
            thread.Start(commit.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
                commit.Commit();
            }
            catch (Exception err) { commit.Rollback(); }
        }
        public static void CommitThread(object co)
        {
            DependentTransaction commit = co as DependentTransaction;
            SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
            conn2.Open();
            conn2.EnlistTransaction(commit as DependentTransaction);
            DisplayTransactioninfo.Display(commit);
            SqlCommand command = new SqlCommand("insert into test values(111)", conn2);
            try
            {
                command.ExecuteNonQuery();
                commit.Complete();
            }
            catch (Exception err) { Console.WriteLine(err); commit.Rollback(); }
        }
    }

 

 

我们用一个子线程来执行另外的一个事务处理,由于是依赖事务处理,所以主事务处理完成后要等待子事务处理的结果。其实本例子已经是涉及到分布式事务处理的范围了,当事务范围内有一个以上的资源管理器时,本地事务管理器将自动提升为DTC管理器,下面我们来看看分布式事务处理。

 

DTC(Distributed Transaction Coordinator) 分布式事务处理

 分布式事务在开发中经常是被用到,也必须被用到。必须同步数据、上下文更新等等。

按照使用方式的不同分布式事务的复杂程度也不同,基于本地事务的多资源管理器和基于SOA的面向服务的多资源管理器。

由于本地事务处理是基于本地事务管理器的,所以它不能管理分布式的事务,一旦当我们处理的事务范围要进行扩大时并且是夸机器的访问时,那么本地事务管理器将自动提升为分布式事务管理器也就是DTC(分布式事务协调器)。

6

 

public class Test4
    {
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        CommittableTransaction committran = new CommittableTransaction();
        public Test4()
        {
            DisplayTransactioninfo.Display(committran);
            conn1.Open();
            conn1.EnlistTransaction(committran);
            conn2.Open();
            conn2.EnlistTransaction(committran);
            DisplayTransactioninfo.Display(committran);
        }
        public void Add4()
        {
            try
            {
                SqlCommand command1 = new SqlCommand("insert into test2 values(111)", conn1);
                command1.ExecuteNonQuery();
                SqlCommand command2 = new SqlCommand("insert into test values(222)", conn2);
                command2.ExecuteNonQuery();
            }
            catch (Exception err) { Console.WriteLine(err); committran.Rollback(); }
        }
    }

 

 

一旦我们开启分布式事务处理就能在我们的电脑上的DTC管理器上看见到。

 

但是要记得检查你的DTC服务是否开启了。

基于WCF框架的分布式事务处理

其实基于WCF框架进行分布式事务开发真的很轻松,它能很好的感知到当前上下文是不是事务域,并能很好的将事务序列化到服务这边来。但是设计一个高性能的分布式事务处理框架并非易事,需要很长时间的积累和实践。我们来看一下WCF是如果进行分布式事务处理的。

7

//服务契约(ServiceContract):
[ServiceContract(SessionMode = SessionMode.Required)]
    public interface IDistributedTransaction
    {
        [TransactionFlow(TransactionFlowOption.Allowed)]
        [OperationContract]
        void Add();
    }

 

 

//服务类1:
public class DistributedTransactionService1 : IDistributedTransaction
    {
        SqlConnection conn1 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        #region IDistributedTransaction
        [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
        public void Add()
        {
            conn1.Open();
            SqlCommand command = new SqlCommand("insert into test2 values(111)", conn1);
            command.ExecuteNonQuery();
            DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current);
        }
        #endregion
    }

 

 

//服务类2:
public class DistributedTransactionService2 : IDistributedTransaction
    {
        SqlConnection conn2 = new SqlConnection("data source=.;Initial Catalog=DataMedicine;Integrated Security=SSPI");
        #region IDistributedTransaction
        [OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
        public void Add()
        {
            conn2.Open();
            SqlCommand command = new SqlCommand("insert into test values(222)", conn2);
            try
            {
                DataBaseOperation.DisplayTransactioninfo.Display(System.Transactions.Transaction.Current);
                command.ExecuteNonQuery();
            }
            catch (Exception err) { throw err; }
        }

 

 

 服务配置:

<service name="ServerConsole.Transaction.DistributedTransactionService1" behaviorConfiguration="metadatabehaviors">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8027"/>
            <add baseAddress="net.tcp://localhost:8028"/>
          </baseAddresses>
        </host>
        <endpoint address="DistributedTransactionService1" binding="netTcpBinding" bindingConfiguration="tranbinding"
                   contract="ServerConsole.Transaction.IDistributedTransaction"></endpoint>
      </service>
      <service name="ServerConsole.Transaction.DistributedTransactionService2" behaviorConfiguration="metadatabehaviors">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8029"/>
            <add baseAddress="net.tcp://localhost:8030"/>
          </baseAddresses>
        </host>
        <endpoint address="DistributedTransactionService2" binding="netTcpBinding" bindingConfiguration="tranbinding"
                contract="ServerConsole.Transaction.IDistributedTransaction"></endpoint>
      </service>

Binding配置:

<bindings>
      <netTcpBinding>
          <binding name="tranbinding" transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004">
          <reliableSession enabled="true" ordered="true"/>
        </binding>
      </netTcpBinding>
    </bindings>

 

 我们需要打开Binding的事务流传递。

客户端代码:

DistributedTransactionClient.DistributedTransactionClient tranclient = new DistributedTransactionClient.DistributedTransactionClient();
            DistributedTransaction2Client.DistributedTransactionClient tranclient2 = new DistributedTransaction2Client.DistributedTransactionClient();
            using (TransactionScope transcope = new TransactionScope())
            {
                try
                {
                    Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);
                    tranclient.Add();
                    tranclient2.Add();
                    transcope.Complete();
                }
                catch (Exception err) { Transaction.Current.Rollback(); }
            }

static void Current_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            if (e.Transaction.TransactionInformation.Status ==
                System.Transactions.TransactionStatus.Committed)
                Console.WriteLine(e.Transaction.TransactionInformation.DistributedIdentifier);
        }

 

 客户端使用TransactionScope类来进行环境事务的设置,这样就很方便知道事务的执行范围,在TransactionScope里面我们可以通过Transaction.Current获取到当前上下文的事务对象,由于事务对象是存储在线程独立存储区里的,所以跨线程访问是没用的,通过依赖事务进行传递。[王清培版权所有,转载请给出署名]

 文章到这里就讲完了,从本地事务、多资源管理器分布式事务、SOA结构的分布式事务,我们都能进行基本的掌握。上面的例子都是经过严格测试的。

时间: 2024-10-11 14:44:14

.NET简谈事务、分布式事务处理的相关文章

.NET简谈事务本质论

这篇文章主要介绍一下事务处理的本质. 其实事务处理对我们来说并不陌生,但是很多人对事务处理的理解似乎有点弄不清,觉得事务处理只存在于数据库中.导致这样的结果是有原因的,当我们开始准备学习编程的时候,都是从某些编程语言开始学起,而不像人家的国外会先从概念.原理.模型开始学习,所以我们都会将某些技术与一些语言.平台联系在一起,导致我们学习其他的语言或者平台会很吃力. 在学校里也好还是自学也好,为了很快的上手都会去学习一些工具然后才会慢慢的去学习跟我们日常开发有关系的技术,仅仅是技术实现而不会去追根究

.NET简谈自定义事务资源管理器

在上一篇文章"NET简谈事务.分布式事务处理"中我大概总结了关于.NET中的事务处理方式和结合了WCF框架的简单应用.在事务性操作中我们的重点是能将数据进行可逆化,说白了就是能保证数据的ACID(关于事务的整体模型.原理请参见".NET简谈事务本质论"一文),在.NET事务处理框架中强大的类库帮我们实现了很多事务传递.事务自动提升的技术难点,同时也提供了很多扩展接口,只要我们肯去研究总能有收获. 这篇文章主要讲解怎样利用.NET为我们提供的扩展接口进行自定义的事务处

分布式事务处理

分布式|事务处理 分布式事务处理张健姿 01-6-22 下午 04:48:27 美 国Sybase 公 司 于 今 年 七 月 发 布 了PowerBuilder 6.0 的Beta 版, 正 式 的 版 本也 将 于 不 久 的 将 来 推 出, 其 中 对 分 布 式 事 务 处 理 的 支持 是 新 版 本 中 增 强 得 最 多 的 功 能. 早 在1995 年,PowerSoft 公司 就 提 出 了 在" 分 布 式 事 务" 方 面 的 发 展 战 略, 并 在1996

ORA-02409:超时:分布式事务处理等待锁定ORA-02063

ORA-02409:超时:分布式事务处理等待锁定ORA-02063 一.错误现象与环境     前端应用程序运行时出现下面的错误提示:         事件添加失败:ORA-02409;超时:分布式事务处理等待锁定         ORA-02063:紧接着line(源于ITSPFDB.US.ORACLE.COM)            该应用程序后台对应的数据库为db01(oraclce 8),使用了db link到数据库itspfdb(oracle 9)     下面的SQL信息来自Orac

信息架构中信息类粒化简谈

当我们进一座写字楼的时候,找你想要去的房间,你会依据什么来指引到那个房间?当你到超市买东西的时候,什么东西指引你可以很快的找到你要去物柜?当你到达一个网站的时候,你依靠什么找到你要的信息?什么样的方式可以让你更加快捷的找到你所关注的目标信息?这时候,一个写字楼的楼层房间分布图谱,一个超市的物品分类,一个网站的信息架构就显示出它的作用了. 当我们找那个目标房间具体信息,房间的位置,朝南还是朝北,房间大小,距离电梯的距离等等,超市找物柜里面的物品,找红酒还是白酒,什么品牌,价格,是否有促销,是自己品

.NET简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算是给初学者提个醒吧. 1: 2: 我们将计算机系统抽象层三个层次,我们做应用层

一起谈.NET技术,.NET简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算是给初学者提个醒吧. 1: 2:  我们将计算机系统抽象层三个层次,我们做应

.NET“.NET研究”简谈面向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算上海闵行企业网站设计与制作是给初学者提个醒吧. 1: 2:  我们将计算机系

.NET简谈面“.NET技术”向接口编程

过程式的开发方式已逐渐退出大众的眼线,随之而来的是各种各样的高抽象的开发模式:我们不得不承认在没有设计模式的时候,我们很难总结出有价值的开发模型,便于以后重复使用和推广:面向对象的流行,让我们开发人员重新站在一个高的起点来看待软件模型,抽象固然是好事,但是也给初学者带来了迷惑,将软件中的东西都想成很简单的封装,我们只需要调用就行,这样越来越多的开发人员开始慢慢的往上浮,有一定编程经验和感触的人,能够明白我所说的浮,也算是给初学者提个醒吧. 1: 2:  我们将计算机系统抽象层三个层次,我们做应