【转】System.Transactions类
========================================================================================
用 using 语句定义了一段隐性事务。如果在该语句块中加入一段对 SQL Server 操作的代码,那么它们将会自动加入这个事务。
void wxd()
{
//开始主事物区
using (System.Transactions.TransactionScope obj_t = new System.Transactions.TransactionScope())
{
System.Data.SqlClient.SqlConnection c = new System.Data.SqlClient.SqlConnection(@"Data Source=WXWINTER\sqlexpress;Initial Catalog=wwxxdd;Integrated Security=True");
System.Data.SqlClient.SqlCommand s = new System.Data.SqlClient.SqlCommand();
s.Connection = c;
s.CommandType = System.Data.CommandType.Text;
c.Open();
//1.事务中查询类
s.CommandText = string.Format("select * from wxd.table_1 ");
s.ExecuteNonQuery(); //执行查询
//此时可对该表进行Select作,无法对表U被锁
MessageBox.Show("停一下,试一试,在数据库里可以Select,无法Insert,Update");
//2.事务中插入类
s.CommandText =string.Format ( "insert into wxd.table_1 (a,b) values ('{0}','8')",DateTime.Now.ToString());
s.ExecuteNonQuery(); //插入记录
//此时无法对该表进行Select 等操作,表已被锁
MessageBox.Show("停一下,试一试,在数据库里无法Select,Insert,Update");
//3.事务中修改类
s.CommandText = string.Format("update top (2) wxd.table_1 set b='{0}'", DateTime.Now.ToString());
s.ExecuteNonQuery(); //修改记录
//此时无法对该表进行Select 等操作,表已被锁
MessageBox.Show("停一下,试一试,在数据库里无法Select,Insert,Update");
////////////////////////////////////////////////////////////////
//在执行开始后,没提交,或取消事物前,
//除[1.事务中查询类]与其并行的Select操作外, 所有对表的操作将等待
int state;
state = 0;
if (state == 0)
{
obj_t.Complete(); //提交
}
else
{
obj_t.Dispose(); //取消
}
//说明:如果不提交出了事物区后,自动回滚
}
}
自定义事务
/////////////////////////////////////////////////////////
class SampleEnlistment1 : System.Transactions.IEnlistmentNotification
{
void System.Transactions.IEnlistmentNotification.Commit(System.Transactions.Enlistment enlistment)
{
Console.WriteLine("自定义事物提交");
enlistment.Done();
}
void System.Transactions.IEnlistmentNotification.InDoubt(System.Transactions.Enlistment enlistment)
{
throw new Exception("The method or operation is not implemented.");
}
void System.Transactions.IEnlistmentNotification.Prepare(System.Transactions.PreparingEnlistment preparingEnlistment)
{
throw new Exception("自定义事物操作没有被执行.");
preparingEnlistment.Prepared();
}
void System.Transactions.IEnlistmentNotification.Rollback(System.Transactions.Enlistment enlistment)
{
Console.WriteLine("自定义事物回滚");
enlistment.Done();
}
}//SampleEnlistment1类
void y() //执行
{
using (System.Transactions.TransactionScope ts = new System.Transactions.TransactionScope())
{
//向事务管理器进行注册,把自定义事务加入到当前事务中去
SampleEnlistment1 obj = new SampleEnlistment1();
System.Transactions.Transaction.Current.EnlistVolatile(obj, System.Transactions.EnlistmentOptions.None);
ts.Complete();
//执行这一段代码,我们可以得到以下的输出:
//准备!
//提交!
//
//而如果将前面的ts.Complete() 行注释掉,显然执行结果就将变为:
//回滚!
//
//////////////////说明//////////////////////////////
//当调用ts.Complete() 方法的时候,表示事务已成功执行。
//随后,事务管理器就会寻找当前所有已注册的条目,
//也就是IEnlistmentNotification 的每一个实现,
//依次调用它们的Prepare 方法,即通知每个条目做好提交准备,
//当所有条目都调用了Prepared() 表示自己已经准备妥当之后,
//再依次调用它们的Commit 方法进行提交。
//如果其中有一个没有调用Prepared 而是调用了ForceRollback 的话,整个事务都将回滚,
//此时事务管理器再调用每个条目的Rollback 方法
}
}//y方法
自定义可参与事物的对象
===========================================
//一个存值类,
//可用[方法_参与事务]方法为对象的成员[对象值]赋值
//[方法_参与事务]可参与事物
public class 对象_参与事务
{
public int 对象值; //该类的存值对象
public Guid 事物ID = Guid.NewGuid(); //该类的[方法_参与事务]参与事物时的ID
//该方法做用是为该对象的[对象值]赋值,该方法可参与事物
public void 方法_参与事务(int 要赋的值)
{
Console.WriteLine("事务GUID号:" + 事物ID.ToString());
//-----------块--------------------------------
//定义与协同该类参与事物的[ 事务处理环节]类对象
事务处理环节 obj事物处理环节= new 事务处理环节(this, 要赋的值);
//将该[ 事务处理环节]类的对象添加到当前事物中
Transaction.Current.EnlistDurable(事物ID, obj事物处理环节, EnlistmentOptions.None);
//
//该操作也可以在该类的外部完成,在using (TransactionScope)里,见<自定义事务>
//^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^^^^^^^^
//------------------块-----------------------------
对象值= 要赋的值; //完成该方法的基本业务,赋值
Console.WriteLine("[对象值]为:" + 对象值.ToString() + " | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚");
//
//该赋值操作也可在[ 事务处理环节]类的提交事物方法中完成,见[事务处理环节.Commit]的[块]
//
//在此处赋值的思路是:方法先赋值,提交不作操作,回滚时将备份的值写回
//在[事务处理环节.Commit]赋值的思路:提交时再赋值。不提交就不会有操作,回滚时不用处理
//两种方式各有应用场景。
//^^^^^^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^^^^^^^
}
}//对象_参与事务
public class 事务处理环节 : IEnlistmentNotification
{
private 对象_参与事务 obj对象_参与事务; //用于存放构造时传入的[对象_参与事务]对象
private int 旧值; //保存[对象_参与事务.对象值]在事件中赋值前的原值
private int 新值; //保存[对象_参与事务.对象值]在事件中所赋的值
//构造函数
public 事务处理环节(对象_参与事务 obj, int value)
{
obj对象_参与事务= obj;
旧值= obj.对象值;
新值= value;
}
//提交事物
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
//------------------块----------------------
//
// obj对象_参与事务.对象值= 新值;
// Console.WriteLine("[对象值]为:" + obj对象_参与事务.对象值.ToString() + " | 此时[对象值]已为所赋的值,事物已提交");
//
//该赋值操作也可在[对象_参与事务.方法_参与事务]完成,见[对象_参与事务.方法_参与事务]的[块]
//
//在此处赋值的思路是:提交时再赋值。不提交就不会有操作,回滚时不用处理
//在原方法中赋值的思路:方法先赋值,提交不作操作,回滚时将备份的值写回
//两种方式各有应用场景。
//^^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^
enlistment.Done();
Console.WriteLine("自定义事物已提交");
}
//存在无法协调同步的问题
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
throw new Exception("自定义事物操作没有被执行.");
}
//准备提交事物
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("自定义事物准备中.......");
preparingEnlistment.Prepared();
}
//回滚事物
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
//---------------块1-----------------
// <回对应[对象_参与事务.方法_参与事务] 中赋值的回滚方案>
//
//此处用的回滚方案是将备份值写回
//
obj对象_参与事务.对象值= 旧值;
//
//
//^^^^^^^^^^^^^^^^END 块1^^^^^^^^^^^^^^^^
Console.WriteLine("自定义事物回滚,[对象值]为:" + obj对象_参与事务.对象值.ToString() + " |此处用的回滚方案是将备份值写回");
enlistment.Done();
}
}//自定义事务处理环节
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点2:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
Console.WriteLine("监视点3:事务区代码执行完毕,准备提交或回滚");
obj_TS.Complete(); //提交事物
Console.WriteLine("监视点4:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000); //关于此处为何要停,见<事物的线程>
Console.WriteLine("监视点5:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("-------我是一条线,我应该出现在最后-------------");
}
========================
提交结果显示
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:4470f0c7-908b-4961-88c9-1262875d878b
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:456
自定义事物准备中.......
自定义事物已提交
监视点5:(事物区后第一行代码)[对象值]为:456
-------我是一条线,我应该出现在最后-------------
=============================
回滚结果显示
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:fa208be7-dec5-4965-ab8d-047f4be1cf79
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:456
自定义事物回滚,[对象值]为:123 |此处用的回滚方案是将备份值写回
监视点5:(事物区后第一行代码)[对象值]为:123
----------我是一条线,我应该出现在最后----------
上例中,如在事物用中多次调用赋值方法,然后回滚
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
wxd.方法_参与事务(1);
wxd.方法_参与事务(2);
wxd.方法_参与事务(3);
wxd.方法_参与事务(4);
wxd.方法_参与事务(5);
Console.WriteLine("监视点:事务区代码执行完毕,准备提交或回滚");
//obj_TS.Complete(); //提交事物
Console.WriteLine("监视点:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000); //关于此处为何要停,见<>
Console.WriteLine("监视点:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("--------我是一条线,我应该出现在最后------------");
}
==============================================
结果如下:
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:1 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:2 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:3 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:4 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:5 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:5
自定义事物回滚,[对象值]为:123 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:456 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:1 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:2 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:3 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:4 |此处用的回滚方案是将备份值写回
监视点5:(事物区后第一行代码)[对象值]为:4 <没有回滚回初始的123>
-------------------我是一条线,我应该出现在最后-----------------------
==========解决方案见下贴============为了,在事物用中多次调用赋值方法,然后回滚
如此改动
====================================================
public class 对象_参与事务
{
public int 对象值;
public Guid 事物ID = Guid.NewGuid();
public void 方法_参与事务(int 要赋的值)
{
Console.WriteLine("事务GUID号:" + 事物ID.ToString());
事务处理环节 obj事物处理环节= new 事务处理环节(this, 要赋的值);
Transaction.Current.EnlistDurable(事物ID, obj事物处理环节, EnlistmentOptions.None);
}
}
public class 事务处理环节 : IEnlistmentNotification
{
private 对象_参与事务 obj对象_参与事务; //用于存放构造时传入的[对象_参与事务]对象
private int 旧值; //保存[对象_参与事务.对象值]在事件中赋值前的原值
private int 新值; //保存[对象_参与事务.对象值]在事件中所赋的值
public 事务处理环节(对象_参与事务 obj, int value)
{
obj对象_参与事务= obj;
旧值= obj.对象值;
新值= value;
}
//提交事物
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
obj对象_参与事务.对象值= 新值;
Console.WriteLine("[对象值]为:" + obj对象_参与事务.对象值.ToString() + " | 此时[对象值]已为所赋的值,事物已提交");
enlistment.Done();
Console.WriteLine("自定义事物已提交");
}
//存在无法协调同步的问题
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
throw new Exception("自定义事物操作没有被执行.");
}
//准备提交事物
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("自定义事物准备中.......");
preparingEnlistment.Prepared();
}
//回滚事物
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
enlistment.Done();
}
}//自定义事务处理环节
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点2:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
wxd.方法_参与事务(1);
wxd.方法_参与事务(2);
wxd.方法_参与事务(3);
wxd.方法_参与事务(4);
wxd.方法_参与事务(5);
Console.WriteLine("监视点3:事务区代码执行完毕,准备提交或回滚");
//obj_TS.Complete(); //提交事物
Console.WriteLine("监视点4:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000);
Console.WriteLine("监视点5:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("------我是一条线,我应该出现在最后------------");
}
=====================================
结果
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:123
监视点5:(事物区后第一行代码)[对象值]为:123
-------------------我是一条线,我应该出现在最后-----------------------
===========================================
实现了多次操作的回滚
此种方法对赋值类可以,但如果是大数据量的操作,将操作放在提交事物的方法中,会有不妥
=======================================
进一步探讨见下贴
在此先回忆一下什么是事物处理
======================================
原子性:是指事务处理的全或无的命题.一旦启动了事务处理,它或是提交,或是放弃.
一致性:要求事务处理结果所做的改变不能够破坏数据的完整性.
隔离性: 一个事务处理中执行的所有操作必须与其他事务处理的操作隔离.
持久性: 是要确保当事务处理提交后,事务处的结果就要得到维持.
=====================================
这时我又想起了该死的COM+
======================================
COM+是COM和MTS的替代技术.COM+是将COM和MTS结合到了一个服务增强包中.它已集成到了2000/2003中,其有如下特点
自动化事务处理
本质上讲,就是当进行事务处理的进候,COM+提供的确保数据据维原子性的机制.这就是说,如果在事务处理中出现了错误,COM+可以自动地回滚事务处理.如果没有出理错误,COM+就会自动提交事务处理.
基于角色的安全
事务处理需要防止非授权的使用.COM+拥有附加的安全特性.它现在可以让用户在方法层次上设定安全角色.
同步
假定有几个人要试图同时使用相同的资源,那么每个人都会妨碍其他人.COM+不仅可以锁定资源,而且还可以完全锁定一个组件,以防止相同的实例同时被一个线程访问.
对象实例化
为了使用MTS实例化对象,开发者发布必须加入附加的代码,来请求MTS建立组件实例.COM+不再需要这些附加的代码.对于COM+,实例化组件所需的工作就是像建立普通对象一样.另外,当请求实例化组件的时候,COM+会自动检测对象的要求,并且提供合适的运行环境.
线程池
是一种资源,用于为其本身维护一池线程.在后台自动完成.
对象池
当启用了对象池的COM+请求一个组件时,COM+会首先检查自已的对象池,并确定是否有对象可以给那个组件.如果有,则服务器会向顾客提供一个引用.这是最典型的情况.如果对象池中的所有对象当前都在使用,COM+将分析自已的最大池尺寸配置,如果没有达到最大尺寸,COM+会创建一个新对象并向顾客传递一个指向这个新对象的引用.当顾客释放对象引用后,COM+会取回对象并放回到对象池中,而不是销毁所释放的对象.
排队组件
允许开发人员建立异步通信的COM组件.QC体系结构内部有四个部分:记录器,MSMQ,监听器,播放器.
COM+事件
为COM组件提供发布事件各预定事件的机制.COM+事件的最大优点是COM类不直接相互捆绑,一切都是通过COM+组件管理器配置的,或在运行时依赖于预订类型.
COM+事件内部有三个部件:发表器,事件系统,预订器
CRM补偿资源管理器!
我前面好像没有提到这个词,补充一下
补偿事务是一种违返隔离性属性的事务.
在补偿事务处理期间,对持久资源的修改,对其他事务是可见的
CRM保证一致性和持久性,事务处理管理器保证原子性.但是CRM并不是内在地保证隔离,所以才会叫作补偿资源管理器.要由CRM开发人员负责保证在事务处理过程中所作的所有修改都对其他事务透明,直到事务处理完成
=================================
在NET中开发COM+,将NET的DLL注册为COM的组件,MS直是变态
先来个COM+的例子吧
========================================================
任务:用NET建立可共享静态对象的COM+服务
说明:
1.COM+服务当然是要支持事物了
2.可共享静态对象,说的是多个应用程序引用同一COM+池中的对象,通过COM+进行通信,共同完成一个事物
///////////
突然想起VB6.0与DCOM了,决定用VB.NET 写这个例子,以记念当年用VB写DCOM与COM+
//////////
''''''''''''''''''''''''''''''改用VB的注释
COM+对象的建立
Imports System.EnterpriseServices '引用COM+名称空间
Imports System.Runtime.InteropServices '为ClassInterface的名称空间
Public Interface icwxd '定义一个接口
Function wxdlzm(ByVal a As String, ByVal i As Integer) As Integer
Property mm() As String
End Interface
'ClassInterface: 定义类接口,如不定义将组件在COM+中设为服务方式时,程序引用时将报接口错误
'Description :
'Transaction :
'Synchronization :
'JustInTimeActivation :
<ClassInterface(ClassInterfaceType.AutoDual), _
Description("该类的说明文字"), _
Transaction(TransactionOption.Required), _
Synchronization(SynchronizationOption.Required), _
JustInTimeActivation(True)> _
Public Class wxd
Inherits System.EnterpriseServices.ServicedComponent '继承COM+服务类
Implements icwxd '继承接口
Dim sss As New DataSet1
Dim q As New sj
'数据库操作的事务处理
<Description("该方法的说明文字"), AutoComplete()> _
Public Function wxdlzm(ByVal a As String, ByVal i As Integer) As Integer Implements icwxd.wxdlzm
Try
'1.从MSMQ中读取所有列队,存入数库中
'2.将一段信息写入磁盘文件
'3.将某数据库中某表的值加a
'4.将某数据库中某表的值减b
'5.发送一条信息到MSMQ中
'对多组 Adapter.Fill(DataSet)的数据更新有效
ContextUtil.SetComplete() '提交事务
Return 0 '返回0告之用户操作成功
Catch ex As Exception '如果上面有一步操作错误
' '1,3,4,5可以回滚
' '2不能回滚
ContextUtil.SetAbort() '回滚事务
Return 1 '返回1告之用户操作失败
End Try
End Function '---------------------------------------------------------------------------------------------------
'静态共享对象
Private Shared m As String = "ddd" '定义一个静态变量,
Public Property mm() As String Implements icwxd.mm '定义一个属性,用于操作静态变量
Get
Return m
End Get
Set(ByVal Value As String)
m = Value
End Set
End Property
End Class
'''''''''''''''''''''''''''''''''''''''''''''''''
添加强名
sn.exe -k c:\a.snk
<Assembly: AssemblyKeyFile("C:\a.snk")>
'''''''''''''''''''''''''''''''''''''''''''''''''''''
注册COM+组件:Regvcs.exe
Regsvcs.exe wxd.dll
该组件必须有强名才能注册.
将产生一个名为wxd.tlb的文件.
在[控制面板]->[管理工具]->[组件服务]->[COM+应用程序]树下将看到注册的组件.
该死的CSDN不能贴图
''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''
新建一程序
'引用该DLL文件
Dim a As New wxd.wxd
a.wxdlzm("dddddddddd111111111", 1) '注上例中的业务处理化码省去了,可自已添
a.mm = "在应用程序A中赋值"
'''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''
再建一程序
'引用该DLL文件
Dim a As New wxd.wxd
Msgbox (a.mm) '得到上个应用程序所赋的值
'''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''
到COM+管理器是查看相应的状态
////////////////////////////////////////////////////
现在,可用
Remoting, 线程池 , System.Transactions 来实现上述功能了
======================================================
该死的CSDN只能发三贴,等有人回了以后再发吧接着写...............
这一贴与技术无关,只谈事物处理的需求演变
看一下如下需求的产生及解决方案
开始事务
1.同一数据库内划账
2.数据库间划账
3.向某用户发信
4.将本地一文件上传到指定服务器
5.将远程主机一目录内容同步到本地目录
6.向连接到本机COM口一电机发出指令,向水池蓄100升水
7.关闭远程一计算机
结束事务
要求,1,2,3,4,5,6,7的操做在同一事物内完成
看一看在解决上述问题时,事物回滚,事物提交,事物准备,事物挂起,事物通知,事物回退,事物回调,事物并行,事物串行,事物资源补偿,资源锁 ,等的问题是如何出现,并如何解决的
///////////////////////////////////////////////////////
先从一个小问题开始..............
假如有这样一个数据库,
/////////////
用户|钱数
a |500
b |600
/////////////
两个用户之间的划账问题,
这里假设不需要或我不会使用交易流水账的方式跟踪交易活动,使用汇总的方式体现钱数的变化。
在这里我且只用update [钱数] 的方式进行用户间的划账.
////////////////////////////////////////
需求1:a用户给b用户划自已支配数额的账
设计如下:
.............................
x1=a要划的账
x2= select 钱数 where a用户
if x2 < x1
update 钱数-x1 where a用户
update 钱数+x1 where b用户
..............................
OK 设计完成! 没有问题! 没有问题..............
但在[update 钱数-x1 where a用户] 后,b用户就消户了,[update 钱数+x1 where b用户]没有执行对象了
代码作如下修改:
.............................
x1=a要划的账
x2= select 钱数 where a用户
if x2 < x1
update 钱数-x1 where a用户
如果 [update 钱数+x1 where b用户] 不成功
update 钱数+x1 where a用户
..............................
OK ,问题解决!
但.....
如果在[update 钱数-x1 where a用户] 后数据库就当掉
如果[update 钱数-x1 where a用户] 就出错
如果[update 钱数+x1 where a用户] 时数据库当掉
........................
接着改代码,这是一个焦油潭....
以上问题由数据库开发商提供解决方案(他的解决方案以后再说)
事物处理(将操作数据的代码放入存储过程,由数据库处理)
.........................................
BEGIN TRANSACTION 事务
Begin
update 钱数-x1 where a用户
update 钱数+x1 where b用户
If @@error=0
Commit Transaction 事务
Else
Rollback Transaction 事务
End
..........................................
OK ,问题解决!,至少前台开发人员问题解决,再有问题找数据库开发人员或数据库提供商
到此先放前台开发人员一马(一会再烦他)
看一下数据库开发人员要面对的一个问题....
见下贴......现在问需求升级!
该系统的提供商决定每个月定期收一定的用户管理费,并由系统自动划账
系统划账模块的数据库操作代码使用上面完成的模块就行
该功能的添加与现有模块将产生一个冲突
即用户划账与系统划账在一个千载难逢的时刻,同时运行了..........
(当然上面的代码可以解决这个冲突,这个例子只是把一系例问题分解了)
这种情况有很多意想不到的情况发生,比如用户的钱为负数了,而系统是不许用户透支的....
锁!同一时刻只能由一个事物来控制资源,数据库开发人员想到了解决方案,但实现该方案又使数据库开发人员掉入了焦油潭
数据库开发商在听到了数据库开发人员的抱怨后,终于升级了数据库事物处理机制,为事物处理加上锁,并提供了销的级别
数据库开发商如何实现事物锁,在后面将具体提到,
现在继续数据库开发人员的恶梦.............
收了一次费,用户返映很大,所以决定取消收费,并退还上次所收的费用
数据库开发人员面要写一个模块
update 钱数+100
将收的100元管理费还给用户,
现在又有一个问题,该系统内有1000000000000000000000000000000000000000000个用户,
由于系统使用的是386,处理数度比效慢,这个行为要执行好几天,
如果使用事物,用户在这几天将无法划账,
如果不用事物,中途当机,无法确定那些用户已得到退款,那些还没有...........
数据库开发商即时提供了大数据更新解决方案,在后面将具体提到,
需求永远是变化的,由于系统压力过大,终于又添加了一台主机......
将一部分用户分到另一台主机的数据库中了......
我们前面提到的两个主角,A用户,与B用户也被分到了不同的主机上
A用户与B用户的划账再次让数据库开发人员惆怅.........
数据库开发商又即时提供了分布式事物处现方案,在后面将具体提到.............
数据库开发人员再一次渡过难关..........
可以前台开发人员的难题又来了......................
见下贴......需求的变化是无止境的,是人们无法预见的.....
现在要与其他的金融系统对接,而且要使用WebServicr方式,数据库开发人员第一时间推掉了该工作.
理由是对WebService不熟,而且对方也不可能允许他们在对方的数据库中写存储过程什么的.....
还有对方的数据库是在X系统上的Y数据库,与他们现在用的不同....
前台开发人员现在要面对数据操作了,这种数据操作是数据库开发人员所不熟的,
于是他们做了分工,一部分人写前台,一部分人写前台与数据库之间的部分,有人管他叫中间层,写中间层的人就叫后台开发人员
于是现在有了 前台开发人员,中间层开发人员,数据库开发人员.
开发模式也进入了三层开发模式
为了解决跨系统的事物处理,
ASP.NET的事物处理出现了.....................
下贴.....接着写.....
在http://community.csdn.net/Expert/topic/4995/4995184.xml?temp=.5229456里发泻了一下
心情好多了
///
ASP.NET的事物处理出现了.....................
///
写错了,应是ADO.NET的事物处理
ADO.NET操作数据库时支持事务,严格的说应是ADO.NET在连接支持事务的数据库时,支持事务
ADO.NET 在连接Access时不"直接"支持事物,因为ADO.NET操作Access时,不能像操作SQL Server那样直接
使用数据库的事务引擎
ADO.NET不是第一个可以在数据库以外使用事物的
以前有DTS,MTS,还有上面说的COM+都可以在数据库以外支持事物
数据库以外支持事物,我想描绘的就是对数据库的事物操作不放在存储过程里,而是在程序代码中实现,
先来个ADO.NET的例子
'''''''''''''''''''''我还用VB.NET写(^_^)'''''''''''''''''''''''''
Dim 事务 As SqlTransaction '定义变量a为事务型
SqlConnection1.Open() 'SqlConnection1对象打开连接
事务 = SqlConnection1.BeginTransaction("okok")
' '用SqlConnection1对象的BeginTransaction方法初始化[事务],
' '参数okok为事务的名()
SqlCommand1.Transaction = 事务
Try '定义一个错误处理,监视两条SQL语句的执行情况
'指定SqlCommand对象要执行的SQL语句,并执行
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
'------------------------------------------------
'再次指定SqlCommand对象要执行的SQL语句,并执行
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
'------------------------------------------------
事务.Commit() '提交事物,既两次的SQL语句
Catch ex As Exception '如果两次的SQL语句有一个执行错误
事务.Rollback("okok") '回滚事物,参数okok为要回滚的事物名.
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
以上实现了对数据库操作的代码层事务处理,
作用与下面的方式相似
''''''''''''''''数据库中'''''''''''''''''
存储过程名
事务
DELETE FROM a WHERE (a = 1)
DELETE FROM a WHERE (a = 2)
结束事务
''''''''''''''''代码中''''''''''''''''''''''
SqlCommand1.CommandText = "存储过程名"
SqlCommand1.ExecuteNonQuery()
'''''''''''''''''''''''''''''''''''''''''
那为何要写到程序中,
其实对于上例,我强列建议写在数据库的存储过程中,而不在代码中实现
可是如果要协同操作的数据库是两个以下,比如一个是Access,一个是Sqlserver,一个是oracle,要将在这
三个数据库中的三条记录的修改放到同一个事物中完成,该如何
当然,在SQL Server的存储过程中也可以实现,
但这种操作,本人觉得还是写一个后台代码操作的中间层好一些
看一下例子
(上例中三类数据库事物协同,有点复杂,特别是还有一个内核不支持事务的Access,这个的解决下面会
提到, 这里先用三个SQL Server来举例)
三个SQL Server 当然要三个连接了
SqlConnection1
SqlConnection2
SqlConnection3
还得有三个各自独立的事务
SqlTransaction_事物1
SqlTransaction_事物2
SqlTransaction_事物3
然后分别绑定
SqlTransaction_事物1 = SqlConnection1.BeginTransaction("事物1")
SqlTransaction_事物2 = SqlConnection1.BeginTransaction("事物2")
SqlTransaction_事物3 = SqlConnection1.BeginTransaction("事物3")
还得有三个执行SQL语句的SqlCommand
SqlCommand1.Transaction = SqlTransaction_事物1
SqlCommand2.Transaction = SqlTransaction_事物2
SqlCommand3.Transaction = SqlTransaction_事物3
接下来写代码,错误扑获用并行嵌套都可,具体问题具体分析
Try
'对第1个数据库操作
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
'对第2个数据库操作
SqlCommand2.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand2.ExecuteNonQuery()
SqlCommand2.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand2.ExecuteNonQuery()
'对第3个数据库操作
SqlCommand3.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand3.ExecuteNonQuery()
SqlCommand3.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand4.ExecuteNonQuery()
SqlTransaction_事物1.Commit()
SqlTransaction_事物2.Commit()
SqlTransaction_事物3.Commit()
Catch ex As Exception
SqlTransaction_事物1.Rollback("事物1")
SqlTransaction_事物2.Rollback("事物2")
SqlTransaction_事物3.Rollback("事物3")
End Try
当然
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
这种操作,应该写到数据库的存储过程中,以下面这种方法操作
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
Try
'对第1个数据库操作
SqlCommand1.CommandText = "存储过程"
SqlCommand1.ExecuteNonQuery()
'对第2个数据库操作
SqlCommand2.CommandText = "存储过程"
SqlCommand2.ExecuteNonQuery()
'对第3个数据库操作
SqlCommand2.CommandText = "存储过程"
SqlCommand2.ExecuteNonQuery()
SqlTransaction_事物1.Commit()
SqlTransaction_事物2.Commit()
SqlTransaction_事物3.Commit()
Catch ex As Exception
SqlTransaction_事物1.Rollback("事物1")
SqlTransaction_事物2.Rollback("事物2")
SqlTransaction_事物3.Rollback("事物3")
End Try
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
上面只是一个思路,至于保存点,事物嵌套,条件回滚都是ADO.NET的基本操作,这里就不多说了
我们还把话题回到System.Transactions类上
.........下贴...................
==============
现在,事情好像都已经有解决方案了
在数据层(我先这么描术),可以在数据库的存储过程中用使用事务
在中间层(或者叫逻辑层,我先这么描术),可以用DTS,MTS,COM+
在前台(或都叫表示层,我先这么描术),可以用ADO.NET的事物机制
当然,ADO.NET也可以写中间层,COM+也可放到前台,存储过程中也能用ADO.NET,COM+
可是不管如何,事情好像都已经有解决方案了,要System.Transactions出来干吗
因为有的数据库不支持事务............
因为写代码不只是为了操作数据库...............
相信大家都有类似这样的经历:
将一个大目录(有N多子目录与文件)剪切到另一个磁盘上(可能要60分钟以上),
如果这时当机了.....................
如果System.IO类支持事务,以上问题就不用考虑了
其实没有支持事务的IO也不要紧,我们可以自已实现
第一种方式:
1.先将这个大目录复制的要剪切到的磁盘的同级目录,名为temp
2.复制完成后,删除原目录。
3.将temp目录改名为原目录名
这里有两类操作,复制与删除叫过程操作,改名叫瞬间操作。
过程操作,我们认为有明显的阶段性,有发生中断的可有,对资有很大的依赖性
瞬间操作,我们认为无法操制其过程,发生中断的可能性很小,可以认为其不可能在操作进行中出现阶段
性错误。
当然,以是中相对的,操作系统的瞬间操作,对CPU来说可能是一个过程操作,
我们不能把代码的逻辑错误,服务器的电源故障冗余方案,与CPU的过热保护放到一段代码中去实现
也就是说事物处理是靠非事务性代码用逻辑关系实现的,事物也有可能无法回滚事最初状态,
面对这种情况,我们要做的就是合理的划分操作单元,完善逻辑关系(具体后面再细谈)
第二种方式:
对原目录的文件结构建立一个清单,每复制一个文件,就在清单中标记一下,复制完成后再从原目录中删除文件。
好了,不管代码效率如何,总算有了自已IO事物的解决方案,
那我们还要System.Transactions类干吗........
..........见下贴.................
统一的接口,多事物协作的前提
接上...........
/////////////////////////////////////////////////////////
因为是想到什么就写什么,有点乱,以后有时间再整理一下吧
////////////////////////////////////////////////////////
还记得上面提过的Access吗,看我如何实现Access事物操作
(办法很多,举一变态的,当轻松一下了)
以下代码不用VB.NET了,也不有C#,用人类语言
!@#$%%^&*()*^&%$ 人类语言的注释 %#%*&*$^&%^$$$%&^
将Access数据库文件复制到一Temp目录
对Access数据进行一系统列的数据操作
从这里开发监视执行性况(Try)
添加,再添加
删除,又删除
修改,还修改
如果以上操作有不成功的(Catch)
将复制到TEMP目录的数据库复制回原目录,覆盖原Access数据库文件
!@#$%%^&*()*^&%$ 人类语言的注释 %#%*&*$^&%^$$$%&^
OK,聪明的大脑相出了绝妙的解决方案............
现在我们用上面的方案与SQL Server一起完成一个协作事物
''''''''''''''''Sql Server操作直接用上例'''''''''''
''''''''''''''''对Access的操作代码放该放到那里'''''''''''
Try
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SQL事务.Commit()
Catch ex As Exception
SQL事务.Rollback("okok")
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
这样写吗?
''''''''''''''''''''''''''''''''''''''''''''
Try
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
-------------------------------------------------
try
将Access数据库文件复制到一Temp目录
对Access数据进行一系统列的数据操作
从这里开发监视执行性况(Try)
添加,再添加
删除,又删除
修改,还修改
如果以上操作有不成功的(Catch)
将复制到TEMP目录的数据库复制回原目录,覆盖原Access数据库文件
SQL事务.Commit()
Catch
SQL事务.Commit()
---------------------------------------------------
SQL事务.Commit()
Catch ex As Exception
SQL事务.Rollback("okok")
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Ok 没问题,有点乱,但实现了,可以将-------------间的代码提出来,写成一个方法或一个类
但现在有一个问题,上面的操作,我想换一下顺序该怎么办
也就是说先执行操作Access,再操作 Sql Server,操作Access时成成,操作Sql Server时出错了,该如
何,
好像也可以解决,再添加一个对复恢Access的回调。
但要还有更多的自定义事务怎么办,而且这些事务又不是一个人开发的怎么办
当然,所有学过面向对象编程的人都会说"定义统一的接口,在同一逻辑关系中处理"
OK,看一下上贴的最后一句话:
"统一的接口,多对象事物协作的前提"
..............下贴..........
准备写
.........(本文正题:)................
二阶段提交
事务回滚
事务回调(事件)
事务挂起
事务回溯
补偿资源
锁定资源-----------------------说明-------------------------
1.上面有不少打错的的字
2.发现VS 2005 有个问题,就是把代码粘到Word 2003中时会有如下情况
a1=a2
变成
a=a
------------
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
变成
Console.WriteLine("监视点:初始的[对象值]为:" + wxd.对象值.ToString());
-------------
也就是后面的数字没了,不知各位是否有这个问题
所以上面有些代码是在Word里改的,可能有部份错误
3.有些代码是直接在Word中打的,没在VS中打,可能会有单词错误,
4.有时用VB.NET有时用C#,有时直接用语言描绘了,以后会全用C#写,
对"统一的接口"再补充一下,
虽然我们自已实现事物处理在有些时候可能比用System.Transactions类更灵活,
但基于System.Transactions类来自定义事务,可以将所有的自定义事务都放到同一个事物池中
还可以实现自定义事物与某些支持事物的服务器(如Sql Server,BizTalk)协同
///////////////////////////////////////////
完全自定义的事物好像C的指针........
基于System.Transactions类的自定义事务好像NET的GC...........
个人感觉的一种比喻.....与技术方向无关(^_^).................接上上贴............
二阶段提交,事务回滚,事务回调(事件),事务挂起,补偿资源,锁定资源,事务回溯,
概念,高手们都解释过N多遍了,
本人没什么文化,理解有限,
就不说此似是而非的原理,意义,性质,历史,将来,希望,展望,局限,
兄弟没什么文化,也没上过大学(真的),只能用实际应用来举例
1.二阶段提交:
什么意思,就是 "我准备好了吗,你准备好了,是的我也准备好了,提交"
什么是"准备":上例中剪切文件的例子中"将源目录复制到TEMP,将源目录删除"就是准备
什么是"提交": 上例将"TEMP改名就是提交"
准备好了也不见得能提交成功,这也是我前面所说的 “过程操作,瞬间操作”的粒度化分问题
原则上不要在Transactions.IEnlistmentNotification.Commit写里过程操作,
所以前面Transactions解决对象多次被改变后回滚的的例子意义不过是一个演示Transactions工作过程的例子而以
2.回滚,很多解释只是一个恢复原状就过去了。
如何恢复原状,是步骤倒做,状态至回,资源备份,他们不说,咱们一会在后面说
3.事务回调,很多时候子事务的执行要很长一段时间,或者要由其进程或主机完成,这时使用串行可以效率会很低,可以考虑并行,这时就有两个要解决的问题,一是出现错误的子事务通知主事务,二是提到子事物错误通知的主事物通知其他正在执行的子事物。
有点像多线程,其实就是多线程与多进程的操作
4.事务挂起,暂停一个事物,等需要时再接着跑,工作流中常用
是实现补偿资源,事务回溯的前提。
听起来很简单,实现很难,为何这样说,看一下相对论就理解了
5.补偿资源,(^_^)
事物真的是原子性,持久性的吗,
不见得,连原子都不原子了,宇宙都不持久性了,何况事务...............
具体的后面现说,
先举个例子
想到了一个本书上的一个事务的例子(远古人换贝壳),不说了,要真是这样,我想我们现在还是猴子
例子!
一个人在饭店定了20个菜,要请20个朋友
饭店起火了,事物回滚
主人死了,事物回滚
20个朋友都死了,事物回滚
要是有一个朋友有病不能来,或者饭店有一个菜无法做,事物也回滚吗.
补偿一下,事物提交就得了
难道我现在写错了一句,还得把所有贴都删了重写,登一个更证说明不就完了吗
6.锁定资源,高手们举了很多不锁资源的后果,不重复了,
加锁注册,解锁通告,更新通知,死锁这些简单的东西,高手们不屑谈,
我没文化,1+1 这么简单的问题也想显显,咱们后面谈,具体谈,大谈特谈
7.事务回溯
你家是A
你女朋友家是X
你到你女朋友家有
(1)A-B-C-D-E-X
(2)A-B-C-1-2-X
(3)A-B-1-2-3-X
(4)A-1-2-4-5-X
这四条路可以走
当你走第一条路时,E处坏了,不能通过,你是事物回滚,回家
还是退到C处走第(2)条路,还是退到B处走第(3)路
这里我们不谈数据结构,不谈最优算法,不谈链表,二叉树,图
我们要谈回退重试过程中各点的资源管理问题
////////////////////////////////////////////////////////////
----------以上数贴,将事物的来历,目的,意义,应用场景都调侃了一遍
----------对System.Transactions类 的应用场景也有了一个简单交代,
----------下贴正式谈代码实现
用 using 语句定义了一段隐性事务。如果在该语句块中加入一段对 SQL Server 操作的代码,那么它们将会自动加入这个事务。
void wxd()
{
//开始主事物区
using (System.Transactions.TransactionScope obj_t = new System.Transactions.TransactionScope())
{
System.Data.SqlClient.SqlConnection c = new System.Data.SqlClient.SqlConnection(@"Data Source=WXWINTER\sqlexpress;Initial Catalog=wwxxdd;Integrated Security=True");
System.Data.SqlClient.SqlCommand s = new System.Data.SqlClient.SqlCommand();
s.Connection = c;
s.CommandType = System.Data.CommandType.Text;
c.Open();
//1.事务中查询类
s.CommandText = string.Format("select * from wxd.table_1 ");
s.ExecuteNonQuery(); //执行查询
//此时可对该表进行Select作,无法对表U被锁
MessageBox.Show("停一下,试一试,在数据库里可以Select,无法Insert,Update");
//2.事务中插入类
s.CommandText =string.Format ( "insert into wxd.table_1 (a,b) values ('{0}','8')",DateTime.Now.ToString());
s.ExecuteNonQuery(); //插入记录
//此时无法对该表进行Select 等操作,表已被锁
MessageBox.Show("停一下,试一试,在数据库里无法Select,Insert,Update");
//3.事务中修改类
s.CommandText = string.Format("update top (2) wxd.table_1 set b='{0}'", DateTime.Now.ToString());
s.ExecuteNonQuery(); //修改记录
//此时无法对该表进行Select 等操作,表已被锁
MessageBox.Show("停一下,试一试,在数据库里无法Select,Insert,Update");
////////////////////////////////////////////////////////////////
//在执行开始后,没提交,或取消事物前,
//除[1.事务中查询类]与其并行的Select操作外, 所有对表的操作将等待
int state;
state = 0;
if (state == 0)
{
obj_t.Complete(); //提交
}
else
{
obj_t.Dispose(); //取消
}
//说明:如果不提交出了事物区后,自动回滚
}
}
自定义事务
/////////////////////////////////////////////////////////
class SampleEnlistment1 : System.Transactions.IEnlistmentNotification
{
void System.Transactions.IEnlistmentNotification.Commit(System.Transactions.Enlistment enlistment)
{
Console.WriteLine("自定义事物提交");
enlistment.Done();
}
void System.Transactions.IEnlistmentNotification.InDoubt(System.Transactions.Enlistment enlistment)
{
throw new Exception("The method or operation is not implemented.");
}
void System.Transactions.IEnlistmentNotification.Prepare(System.Transactions.PreparingEnlistment preparingEnlistment)
{
throw new Exception("自定义事物操作没有被执行.");
preparingEnlistment.Prepared();
}
void System.Transactions.IEnlistmentNotification.Rollback(System.Transactions.Enlistment enlistment)
{
Console.WriteLine("自定义事物回滚");
enlistment.Done();
}
}//SampleEnlistment1类
void y() //执行
{
using (System.Transactions.TransactionScope ts = new System.Transactions.TransactionScope())
{
//向事务管理器进行注册,把自定义事务加入到当前事务中去
SampleEnlistment1 obj = new SampleEnlistment1();
System.Transactions.Transaction.Current.EnlistVolatile(obj, System.Transactions.EnlistmentOptions.None);
ts.Complete();
//执行这一段代码,我们可以得到以下的输出:
//准备!
//提交!
//
//而如果将前面的ts.Complete() 行注释掉,显然执行结果就将变为:
//回滚!
//
//////////////////说明//////////////////////////////
//当调用ts.Complete() 方法的时候,表示事务已成功执行。
//随后,事务管理器就会寻找当前所有已注册的条目,
//也就是IEnlistmentNotification 的每一个实现,
//依次调用它们的Prepare 方法,即通知每个条目做好提交准备,
//当所有条目都调用了Prepared() 表示自己已经准备妥当之后,
//再依次调用它们的Commit 方法进行提交。
//如果其中有一个没有调用Prepared 而是调用了ForceRollback 的话,整个事务都将回滚,
//此时事务管理器再调用每个条目的Rollback 方法
}
}//y方法
自定义可参与事物的对象
===========================================
//一个存值类,
//可用[方法_参与事务]方法为对象的成员[对象值]赋值
//[方法_参与事务]可参与事物
public class 对象_参与事务
{
public int 对象值; //该类的存值对象
public Guid 事物ID = Guid.NewGuid(); //该类的[方法_参与事务]参与事物时的ID
//该方法做用是为该对象的[对象值]赋值,该方法可参与事物
public void 方法_参与事务(int 要赋的值)
{
Console.WriteLine("事务GUID号:" + 事物ID.ToString());
//-----------块--------------------------------
//定义与协同该类参与事物的[ 事务处理环节]类对象
事务处理环节 obj事物处理环节= new 事务处理环节(this, 要赋的值);
//将该[ 事务处理环节]类的对象添加到当前事物中
Transaction.Current.EnlistDurable(事物ID, obj事物处理环节, EnlistmentOptions.None);
//
//该操作也可以在该类的外部完成,在using (TransactionScope)里,见<自定义事务>
//^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^^^^^^^^
//------------------块-----------------------------
对象值= 要赋的值; //完成该方法的基本业务,赋值
Console.WriteLine("[对象值]为:" + 对象值.ToString() + " | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚");
//
//该赋值操作也可在[ 事务处理环节]类的提交事物方法中完成,见[事务处理环节.Commit]的[块]
//
//在此处赋值的思路是:方法先赋值,提交不作操作,回滚时将备份的值写回
//在[事务处理环节.Commit]赋值的思路:提交时再赋值。不提交就不会有操作,回滚时不用处理
//两种方式各有应用场景。
//^^^^^^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^^^^^^^
}
}//对象_参与事务
public class 事务处理环节 : IEnlistmentNotification
{
private 对象_参与事务 obj对象_参与事务; //用于存放构造时传入的[对象_参与事务]对象
private int 旧值; //保存[对象_参与事务.对象值]在事件中赋值前的原值
private int 新值; //保存[对象_参与事务.对象值]在事件中所赋的值
//构造函数
public 事务处理环节(对象_参与事务 obj, int value)
{
obj对象_参与事务= obj;
旧值= obj.对象值;
新值= value;
}
//提交事物
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
//------------------块----------------------
//
// obj对象_参与事务.对象值= 新值;
// Console.WriteLine("[对象值]为:" + obj对象_参与事务.对象值.ToString() + " | 此时[对象值]已为所赋的值,事物已提交");
//
//该赋值操作也可在[对象_参与事务.方法_参与事务]完成,见[对象_参与事务.方法_参与事务]的[块]
//
//在此处赋值的思路是:提交时再赋值。不提交就不会有操作,回滚时不用处理
//在原方法中赋值的思路:方法先赋值,提交不作操作,回滚时将备份的值写回
//两种方式各有应用场景。
//^^^^^^^^^^^^^^^END 块^^^^^^^^^^^^^^^^^^
enlistment.Done();
Console.WriteLine("自定义事物已提交");
}
//存在无法协调同步的问题
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
throw new Exception("自定义事物操作没有被执行.");
}
//准备提交事物
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("自定义事物准备中.......");
preparingEnlistment.Prepared();
}
//回滚事物
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
//---------------块1-----------------
// <回对应[对象_参与事务.方法_参与事务] 中赋值的回滚方案>
//
//此处用的回滚方案是将备份值写回
//
obj对象_参与事务.对象值= 旧值;
//
//
//^^^^^^^^^^^^^^^^END 块1^^^^^^^^^^^^^^^^
Console.WriteLine("自定义事物回滚,[对象值]为:" + obj对象_参与事务.对象值.ToString() + " |此处用的回滚方案是将备份值写回");
enlistment.Done();
}
}//自定义事务处理环节
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点2:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
Console.WriteLine("监视点3:事务区代码执行完毕,准备提交或回滚");
obj_TS.Complete(); //提交事物
Console.WriteLine("监视点4:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000); //关于此处为何要停,见<事物的线程>
Console.WriteLine("监视点5:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("-------我是一条线,我应该出现在最后-------------");
}
========================
提交结果显示
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:4470f0c7-908b-4961-88c9-1262875d878b
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:456
自定义事物准备中.......
自定义事物已提交
监视点5:(事物区后第一行代码)[对象值]为:456
-------我是一条线,我应该出现在最后-------------
=============================
回滚结果显示
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:fa208be7-dec5-4965-ab8d-047f4be1cf79
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:456
自定义事物回滚,[对象值]为:123 |此处用的回滚方案是将备份值写回
监视点5:(事物区后第一行代码)[对象值]为:123
----------我是一条线,我应该出现在最后----------
上例中,如在事物用中多次调用赋值方法,然后回滚
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
wxd.方法_参与事务(1);
wxd.方法_参与事务(2);
wxd.方法_参与事务(3);
wxd.方法_参与事务(4);
wxd.方法_参与事务(5);
Console.WriteLine("监视点:事务区代码执行完毕,准备提交或回滚");
//obj_TS.Complete(); //提交事物
Console.WriteLine("监视点:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000); //关于此处为何要停,见<>
Console.WriteLine("监视点:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("--------我是一条线,我应该出现在最后------------");
}
==============================================
结果如下:
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:456 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:1 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:2 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:3 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:4 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
事务GUID号:cf90c882-a7e0-4994-8834-db532652f256
[对象值]为:5 | 此时[对象值]已为所赋的值,但事物还没提交,所以该值可能被回滚
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:5
自定义事物回滚,[对象值]为:123 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:456 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:1 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:2 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:3 |此处用的回滚方案是将备份值写回
自定义事物回滚,[对象值]为:4 |此处用的回滚方案是将备份值写回
监视点5:(事物区后第一行代码)[对象值]为:4 <没有回滚回初始的123>
-------------------我是一条线,我应该出现在最后-----------------------
==========解决方案见下贴============为了,在事物用中多次调用赋值方法,然后回滚
如此改动
====================================================
public class 对象_参与事务
{
public int 对象值;
public Guid 事物ID = Guid.NewGuid();
public void 方法_参与事务(int 要赋的值)
{
Console.WriteLine("事务GUID号:" + 事物ID.ToString());
事务处理环节 obj事物处理环节= new 事务处理环节(this, 要赋的值);
Transaction.Current.EnlistDurable(事物ID, obj事物处理环节, EnlistmentOptions.None);
}
}
public class 事务处理环节 : IEnlistmentNotification
{
private 对象_参与事务 obj对象_参与事务; //用于存放构造时传入的[对象_参与事务]对象
private int 旧值; //保存[对象_参与事务.对象值]在事件中赋值前的原值
private int 新值; //保存[对象_参与事务.对象值]在事件中所赋的值
public 事务处理环节(对象_参与事务 obj, int value)
{
obj对象_参与事务= obj;
旧值= obj.对象值;
新值= value;
}
//提交事物
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
obj对象_参与事务.对象值= 新值;
Console.WriteLine("[对象值]为:" + obj对象_参与事务.对象值.ToString() + " | 此时[对象值]已为所赋的值,事物已提交");
enlistment.Done();
Console.WriteLine("自定义事物已提交");
}
//存在无法协调同步的问题
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
throw new Exception("自定义事物操作没有被执行.");
}
//准备提交事物
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("自定义事物准备中.......");
preparingEnlistment.Prepared();
}
//回滚事物
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
enlistment.Done();
}
}//自定义事务处理环节
void z()
{
对象_参与事务 wxd = new 对象_参与事务();
wxd.对象值=123; //赋一初始值
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
//开始主事物区
using (TransactionScope obj_TS = new TransactionScope())
{
Console.WriteLine("监视点2:(事物区最后一行代码)进入事务区");
wxd.方法_参与事务(456); //使用事物方法为[wxd.对象值]赋值
wxd.方法_参与事务(1);
wxd.方法_参与事务(2);
wxd.方法_参与事务(3);
wxd.方法_参与事务(4);
wxd.方法_参与事务(5);
Console.WriteLine("监视点3:事务区代码执行完毕,准备提交或回滚");
//obj_TS.Complete(); //提交事物
Console.WriteLine("监视点4:(事物区最后一行代码)[对象值]为:" + wxd.对象值.ToString());
}//结束事物区
System.Threading.Thread.Sleep(1000);
Console.WriteLine("监视点5:(事物区后第一行代码)[对象值]为:" + wxd.对象值.ToString());
Console.WriteLine("------我是一条线,我应该出现在最后------------");
}
=====================================
结果
监视点1:初始的[对象值]为:123
监视点2:(事物区最后一行代码)进入事务区
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
事务GUID号:6cd13301-c232-4371-903f-1d3a782da322
监视点3:事务区代码执行完毕,准备提交或回滚
监视点4:(事物区最后一行代码)[对象值]为:123
监视点5:(事物区后第一行代码)[对象值]为:123
-------------------我是一条线,我应该出现在最后-----------------------
===========================================
实现了多次操作的回滚
此种方法对赋值类可以,但如果是大数据量的操作,将操作放在提交事物的方法中,会有不妥
=======================================
进一步探讨见下贴
在此先回忆一下什么是事物处理
======================================
原子性:是指事务处理的全或无的命题.一旦启动了事务处理,它或是提交,或是放弃.
一致性:要求事务处理结果所做的改变不能够破坏数据的完整性.
隔离性: 一个事务处理中执行的所有操作必须与其他事务处理的操作隔离.
持久性: 是要确保当事务处理提交后,事务处的结果就要得到维持.
=====================================
这时我又想起了该死的COM+
======================================
COM+是COM和MTS的替代技术.COM+是将COM和MTS结合到了一个服务增强包中.它已集成到了2000/2003中,其有如下特点
自动化事务处理
本质上讲,就是当进行事务处理的进候,COM+提供的确保数据据维原子性的机制.这就是说,如果在事务处理中出现了错误,COM+可以自动地回滚事务处理.如果没有出理错误,COM+就会自动提交事务处理.
基于角色的安全
事务处理需要防止非授权的使用.COM+拥有附加的安全特性.它现在可以让用户在方法层次上设定安全角色.
同步
假定有几个人要试图同时使用相同的资源,那么每个人都会妨碍其他人.COM+不仅可以锁定资源,而且还可以完全锁定一个组件,以防止相同的实例同时被一个线程访问.
对象实例化
为了使用MTS实例化对象,开发者发布必须加入附加的代码,来请求MTS建立组件实例.COM+不再需要这些附加的代码.对于COM+,实例化组件所需的工作就是像建立普通对象一样.另外,当请求实例化组件的时候,COM+会自动检测对象的要求,并且提供合适的运行环境.
线程池
是一种资源,用于为其本身维护一池线程.在后台自动完成.
对象池
当启用了对象池的COM+请求一个组件时,COM+会首先检查自已的对象池,并确定是否有对象可以给那个组件.如果有,则服务器会向顾客提供一个引用.这是最典型的情况.如果对象池中的所有对象当前都在使用,COM+将分析自已的最大池尺寸配置,如果没有达到最大尺寸,COM+会创建一个新对象并向顾客传递一个指向这个新对象的引用.当顾客释放对象引用后,COM+会取回对象并放回到对象池中,而不是销毁所释放的对象.
排队组件
允许开发人员建立异步通信的COM组件.QC体系结构内部有四个部分:记录器,MSMQ,监听器,播放器.
COM+事件
为COM组件提供发布事件各预定事件的机制.COM+事件的最大优点是COM类不直接相互捆绑,一切都是通过COM+组件管理器配置的,或在运行时依赖于预订类型.
COM+事件内部有三个部件:发表器,事件系统,预订器
CRM补偿资源管理器!
我前面好像没有提到这个词,补充一下
补偿事务是一种违返隔离性属性的事务.
在补偿事务处理期间,对持久资源的修改,对其他事务是可见的
CRM保证一致性和持久性,事务处理管理器保证原子性.但是CRM并不是内在地保证隔离,所以才会叫作补偿资源管理器.要由CRM开发人员负责保证在事务处理过程中所作的所有修改都对其他事务透明,直到事务处理完成
=================================
在NET中开发COM+,将NET的DLL注册为COM的组件,MS直是变态
先来个COM+的例子吧
========================================================
任务:用NET建立可共享静态对象的COM+服务
说明:
1.COM+服务当然是要支持事物了
2.可共享静态对象,说的是多个应用程序引用同一COM+池中的对象,通过COM+进行通信,共同完成一个事物
///////////
突然想起VB6.0与DCOM了,决定用VB.NET 写这个例子,以记念当年用VB写DCOM与COM+
//////////
''''''''''''''''''''''''''''''改用VB的注释
COM+对象的建立
Imports System.EnterpriseServices '引用COM+名称空间
Imports System.Runtime.InteropServices '为ClassInterface的名称空间
Public Interface icwxd '定义一个接口
Function wxdlzm(ByVal a As String, ByVal i As Integer) As Integer
Property mm() As String
End Interface
'ClassInterface: 定义类接口,如不定义将组件在COM+中设为服务方式时,程序引用时将报接口错误
'Description :
'Transaction :
'Synchronization :
'JustInTimeActivation :
<ClassInterface(ClassInterfaceType.AutoDual), _
Description("该类的说明文字"), _
Transaction(TransactionOption.Required), _
Synchronization(SynchronizationOption.Required), _
JustInTimeActivation(True)> _
Public Class wxd
Inherits System.EnterpriseServices.ServicedComponent '继承COM+服务类
Implements icwxd '继承接口
Dim sss As New DataSet1
Dim q As New sj
'数据库操作的事务处理
<Description("该方法的说明文字"), AutoComplete()> _
Public Function wxdlzm(ByVal a As String, ByVal i As Integer) As Integer Implements icwxd.wxdlzm
Try
'1.从MSMQ中读取所有列队,存入数库中
'2.将一段信息写入磁盘文件
'3.将某数据库中某表的值加a
'4.将某数据库中某表的值减b
'5.发送一条信息到MSMQ中
'对多组 Adapter.Fill(DataSet)的数据更新有效
ContextUtil.SetComplete() '提交事务
Return 0 '返回0告之用户操作成功
Catch ex As Exception '如果上面有一步操作错误
' '1,3,4,5可以回滚
' '2不能回滚
ContextUtil.SetAbort() '回滚事务
Return 1 '返回1告之用户操作失败
End Try
End Function '---------------------------------------------------------------------------------------------------
'静态共享对象
Private Shared m As String = "ddd" '定义一个静态变量,
Public Property mm() As String Implements icwxd.mm '定义一个属性,用于操作静态变量
Get
Return m
End Get
Set(ByVal Value As String)
m = Value
End Set
End Property
End Class
'''''''''''''''''''''''''''''''''''''''''''''''''
添加强名
sn.exe -k c:\a.snk
<Assembly: AssemblyKeyFile("C:\a.snk")>
'''''''''''''''''''''''''''''''''''''''''''''''''''''
注册COM+组件:Regvcs.exe
Regsvcs.exe wxd.dll
该组件必须有强名才能注册.
将产生一个名为wxd.tlb的文件.
在[控制面板]->[管理工具]->[组件服务]->[COM+应用程序]树下将看到注册的组件.
该死的CSDN不能贴图
''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''
新建一程序
'引用该DLL文件
Dim a As New wxd.wxd
a.wxdlzm("dddddddddd111111111", 1) '注上例中的业务处理化码省去了,可自已添
a.mm = "在应用程序A中赋值"
'''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''
再建一程序
'引用该DLL文件
Dim a As New wxd.wxd
Msgbox (a.mm) '得到上个应用程序所赋的值
'''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''
到COM+管理器是查看相应的状态
////////////////////////////////////////////////////
现在,可用
Remoting, 线程池 , System.Transactions 来实现上述功能了
======================================================
该死的CSDN只能发三贴,等有人回了以后再发吧接着写...............
这一贴与技术无关,只谈事物处理的需求演变
看一下如下需求的产生及解决方案
开始事务
1.同一数据库内划账
2.数据库间划账
3.向某用户发信
4.将本地一文件上传到指定服务器
5.将远程主机一目录内容同步到本地目录
6.向连接到本机COM口一电机发出指令,向水池蓄100升水
7.关闭远程一计算机
结束事务
要求,1,2,3,4,5,6,7的操做在同一事物内完成
看一看在解决上述问题时,事物回滚,事物提交,事物准备,事物挂起,事物通知,事物回退,事物回调,事物并行,事物串行,事物资源补偿,资源锁 ,等的问题是如何出现,并如何解决的
///////////////////////////////////////////////////////
先从一个小问题开始..............
假如有这样一个数据库,
/////////////
用户|钱数
a |500
b |600
/////////////
两个用户之间的划账问题,
这里假设不需要或我不会使用交易流水账的方式跟踪交易活动,使用汇总的方式体现钱数的变化。
在这里我且只用update [钱数] 的方式进行用户间的划账.
////////////////////////////////////////
需求1:a用户给b用户划自已支配数额的账
设计如下:
.............................
x1=a要划的账
x2= select 钱数 where a用户
if x2 < x1
update 钱数-x1 where a用户
update 钱数+x1 where b用户
..............................
OK 设计完成! 没有问题! 没有问题..............
但在[update 钱数-x1 where a用户] 后,b用户就消户了,[update 钱数+x1 where b用户]没有执行对象了
代码作如下修改:
.............................
x1=a要划的账
x2= select 钱数 where a用户
if x2 < x1
update 钱数-x1 where a用户
如果 [update 钱数+x1 where b用户] 不成功
update 钱数+x1 where a用户
..............................
OK ,问题解决!
但.....
如果在[update 钱数-x1 where a用户] 后数据库就当掉
如果[update 钱数-x1 where a用户] 就出错
如果[update 钱数+x1 where a用户] 时数据库当掉
........................
接着改代码,这是一个焦油潭....
以上问题由数据库开发商提供解决方案(他的解决方案以后再说)
事物处理(将操作数据的代码放入存储过程,由数据库处理)
.........................................
BEGIN TRANSACTION 事务
Begin
update 钱数-x1 where a用户
update 钱数+x1 where b用户
If @@error=0
Commit Transaction 事务
Else
Rollback Transaction 事务
End
..........................................
OK ,问题解决!,至少前台开发人员问题解决,再有问题找数据库开发人员或数据库提供商
到此先放前台开发人员一马(一会再烦他)
看一下数据库开发人员要面对的一个问题....
见下贴......现在问需求升级!
该系统的提供商决定每个月定期收一定的用户管理费,并由系统自动划账
系统划账模块的数据库操作代码使用上面完成的模块就行
该功能的添加与现有模块将产生一个冲突
即用户划账与系统划账在一个千载难逢的时刻,同时运行了..........
(当然上面的代码可以解决这个冲突,这个例子只是把一系例问题分解了)
这种情况有很多意想不到的情况发生,比如用户的钱为负数了,而系统是不许用户透支的....
锁!同一时刻只能由一个事物来控制资源,数据库开发人员想到了解决方案,但实现该方案又使数据库开发人员掉入了焦油潭
数据库开发商在听到了数据库开发人员的抱怨后,终于升级了数据库事物处理机制,为事物处理加上锁,并提供了销的级别
数据库开发商如何实现事物锁,在后面将具体提到,
现在继续数据库开发人员的恶梦.............
收了一次费,用户返映很大,所以决定取消收费,并退还上次所收的费用
数据库开发人员面要写一个模块
update 钱数+100
将收的100元管理费还给用户,
现在又有一个问题,该系统内有1000000000000000000000000000000000000000000个用户,
由于系统使用的是386,处理数度比效慢,这个行为要执行好几天,
如果使用事物,用户在这几天将无法划账,
如果不用事物,中途当机,无法确定那些用户已得到退款,那些还没有...........
数据库开发商即时提供了大数据更新解决方案,在后面将具体提到,
需求永远是变化的,由于系统压力过大,终于又添加了一台主机......
将一部分用户分到另一台主机的数据库中了......
我们前面提到的两个主角,A用户,与B用户也被分到了不同的主机上
A用户与B用户的划账再次让数据库开发人员惆怅.........
数据库开发商又即时提供了分布式事物处现方案,在后面将具体提到.............
数据库开发人员再一次渡过难关..........
可以前台开发人员的难题又来了......................
见下贴......需求的变化是无止境的,是人们无法预见的.....
现在要与其他的金融系统对接,而且要使用WebServicr方式,数据库开发人员第一时间推掉了该工作.
理由是对WebService不熟,而且对方也不可能允许他们在对方的数据库中写存储过程什么的.....
还有对方的数据库是在X系统上的Y数据库,与他们现在用的不同....
前台开发人员现在要面对数据操作了,这种数据操作是数据库开发人员所不熟的,
于是他们做了分工,一部分人写前台,一部分人写前台与数据库之间的部分,有人管他叫中间层,写中间层的人就叫后台开发人员
于是现在有了 前台开发人员,中间层开发人员,数据库开发人员.
开发模式也进入了三层开发模式
为了解决跨系统的事物处理,
ASP.NET的事物处理出现了.....................
下贴.....接着写.....
在http://community.csdn.net/Expert/topic/4995/4995184.xml?temp=.5229456里发泻了一下
心情好多了
///
ASP.NET的事物处理出现了.....................
///
写错了,应是ADO.NET的事物处理
ADO.NET操作数据库时支持事务,严格的说应是ADO.NET在连接支持事务的数据库时,支持事务
ADO.NET 在连接Access时不"直接"支持事物,因为ADO.NET操作Access时,不能像操作SQL Server那样直接
使用数据库的事务引擎
ADO.NET不是第一个可以在数据库以外使用事物的
以前有DTS,MTS,还有上面说的COM+都可以在数据库以外支持事物
数据库以外支持事物,我想描绘的就是对数据库的事物操作不放在存储过程里,而是在程序代码中实现,
先来个ADO.NET的例子
'''''''''''''''''''''我还用VB.NET写(^_^)'''''''''''''''''''''''''
Dim 事务 As SqlTransaction '定义变量a为事务型
SqlConnection1.Open() 'SqlConnection1对象打开连接
事务 = SqlConnection1.BeginTransaction("okok")
' '用SqlConnection1对象的BeginTransaction方法初始化[事务],
' '参数okok为事务的名()
SqlCommand1.Transaction = 事务
Try '定义一个错误处理,监视两条SQL语句的执行情况
'指定SqlCommand对象要执行的SQL语句,并执行
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
'------------------------------------------------
'再次指定SqlCommand对象要执行的SQL语句,并执行
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
'------------------------------------------------
事务.Commit() '提交事物,既两次的SQL语句
Catch ex As Exception '如果两次的SQL语句有一个执行错误
事务.Rollback("okok") '回滚事物,参数okok为要回滚的事物名.
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
以上实现了对数据库操作的代码层事务处理,
作用与下面的方式相似
''''''''''''''''数据库中'''''''''''''''''
存储过程名
事务
DELETE FROM a WHERE (a = 1)
DELETE FROM a WHERE (a = 2)
结束事务
''''''''''''''''代码中''''''''''''''''''''''
SqlCommand1.CommandText = "存储过程名"
SqlCommand1.ExecuteNonQuery()
'''''''''''''''''''''''''''''''''''''''''
那为何要写到程序中,
其实对于上例,我强列建议写在数据库的存储过程中,而不在代码中实现
可是如果要协同操作的数据库是两个以下,比如一个是Access,一个是Sqlserver,一个是oracle,要将在这
三个数据库中的三条记录的修改放到同一个事物中完成,该如何
当然,在SQL Server的存储过程中也可以实现,
但这种操作,本人觉得还是写一个后台代码操作的中间层好一些
看一下例子
(上例中三类数据库事物协同,有点复杂,特别是还有一个内核不支持事务的Access,这个的解决下面会
提到, 这里先用三个SQL Server来举例)
三个SQL Server 当然要三个连接了
SqlConnection1
SqlConnection2
SqlConnection3
还得有三个各自独立的事务
SqlTransaction_事物1
SqlTransaction_事物2
SqlTransaction_事物3
然后分别绑定
SqlTransaction_事物1 = SqlConnection1.BeginTransaction("事物1")
SqlTransaction_事物2 = SqlConnection1.BeginTransaction("事物2")
SqlTransaction_事物3 = SqlConnection1.BeginTransaction("事物3")
还得有三个执行SQL语句的SqlCommand
SqlCommand1.Transaction = SqlTransaction_事物1
SqlCommand2.Transaction = SqlTransaction_事物2
SqlCommand3.Transaction = SqlTransaction_事物3
接下来写代码,错误扑获用并行嵌套都可,具体问题具体分析
Try
'对第1个数据库操作
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
'对第2个数据库操作
SqlCommand2.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand2.ExecuteNonQuery()
SqlCommand2.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand2.ExecuteNonQuery()
'对第3个数据库操作
SqlCommand3.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand3.ExecuteNonQuery()
SqlCommand3.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand4.ExecuteNonQuery()
SqlTransaction_事物1.Commit()
SqlTransaction_事物2.Commit()
SqlTransaction_事物3.Commit()
Catch ex As Exception
SqlTransaction_事物1.Rollback("事物1")
SqlTransaction_事物2.Rollback("事物2")
SqlTransaction_事物3.Rollback("事物3")
End Try
当然
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 2)"
SqlCommand1.ExecuteNonQuery()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
这种操作,应该写到数据库的存储过程中,以下面这种方法操作
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
Try
'对第1个数据库操作
SqlCommand1.CommandText = "存储过程"
SqlCommand1.ExecuteNonQuery()
'对第2个数据库操作
SqlCommand2.CommandText = "存储过程"
SqlCommand2.ExecuteNonQuery()
'对第3个数据库操作
SqlCommand2.CommandText = "存储过程"
SqlCommand2.ExecuteNonQuery()
SqlTransaction_事物1.Commit()
SqlTransaction_事物2.Commit()
SqlTransaction_事物3.Commit()
Catch ex As Exception
SqlTransaction_事物1.Rollback("事物1")
SqlTransaction_事物2.Rollback("事物2")
SqlTransaction_事物3.Rollback("事物3")
End Try
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
上面只是一个思路,至于保存点,事物嵌套,条件回滚都是ADO.NET的基本操作,这里就不多说了
我们还把话题回到System.Transactions类上
.........下贴...................
==============
现在,事情好像都已经有解决方案了
在数据层(我先这么描术),可以在数据库的存储过程中用使用事务
在中间层(或者叫逻辑层,我先这么描术),可以用DTS,MTS,COM+
在前台(或都叫表示层,我先这么描术),可以用ADO.NET的事物机制
当然,ADO.NET也可以写中间层,COM+也可放到前台,存储过程中也能用ADO.NET,COM+
可是不管如何,事情好像都已经有解决方案了,要System.Transactions出来干吗
因为有的数据库不支持事务............
因为写代码不只是为了操作数据库...............
相信大家都有类似这样的经历:
将一个大目录(有N多子目录与文件)剪切到另一个磁盘上(可能要60分钟以上),
如果这时当机了.....................
如果System.IO类支持事务,以上问题就不用考虑了
其实没有支持事务的IO也不要紧,我们可以自已实现
第一种方式:
1.先将这个大目录复制的要剪切到的磁盘的同级目录,名为temp
2.复制完成后,删除原目录。
3.将temp目录改名为原目录名
这里有两类操作,复制与删除叫过程操作,改名叫瞬间操作。
过程操作,我们认为有明显的阶段性,有发生中断的可有,对资有很大的依赖性
瞬间操作,我们认为无法操制其过程,发生中断的可能性很小,可以认为其不可能在操作进行中出现阶段
性错误。
当然,以是中相对的,操作系统的瞬间操作,对CPU来说可能是一个过程操作,
我们不能把代码的逻辑错误,服务器的电源故障冗余方案,与CPU的过热保护放到一段代码中去实现
也就是说事物处理是靠非事务性代码用逻辑关系实现的,事物也有可能无法回滚事最初状态,
面对这种情况,我们要做的就是合理的划分操作单元,完善逻辑关系(具体后面再细谈)
第二种方式:
对原目录的文件结构建立一个清单,每复制一个文件,就在清单中标记一下,复制完成后再从原目录中删除文件。
好了,不管代码效率如何,总算有了自已IO事物的解决方案,
那我们还要System.Transactions类干吗........
..........见下贴.................
统一的接口,多事物协作的前提
接上...........
/////////////////////////////////////////////////////////
因为是想到什么就写什么,有点乱,以后有时间再整理一下吧
////////////////////////////////////////////////////////
还记得上面提过的Access吗,看我如何实现Access事物操作
(办法很多,举一变态的,当轻松一下了)
以下代码不用VB.NET了,也不有C#,用人类语言
!@#$%%^&*()*^&%$ 人类语言的注释 %#%*&*$^&%^$$$%&^
将Access数据库文件复制到一Temp目录
对Access数据进行一系统列的数据操作
从这里开发监视执行性况(Try)
添加,再添加
删除,又删除
修改,还修改
如果以上操作有不成功的(Catch)
将复制到TEMP目录的数据库复制回原目录,覆盖原Access数据库文件
!@#$%%^&*()*^&%$ 人类语言的注释 %#%*&*$^&%^$$$%&^
OK,聪明的大脑相出了绝妙的解决方案............
现在我们用上面的方案与SQL Server一起完成一个协作事物
''''''''''''''''Sql Server操作直接用上例'''''''''''
''''''''''''''''对Access的操作代码放该放到那里'''''''''''
Try
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
SQL事务.Commit()
Catch ex As Exception
SQL事务.Rollback("okok")
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
这样写吗?
''''''''''''''''''''''''''''''''''''''''''''
Try
SqlCommand1.CommandText = "DELETE FROM a WHERE (a = 1)"
SqlCommand1.ExecuteNonQuery()
-------------------------------------------------
try
将Access数据库文件复制到一Temp目录
对Access数据进行一系统列的数据操作
从这里开发监视执行性况(Try)
添加,再添加
删除,又删除
修改,还修改
如果以上操作有不成功的(Catch)
将复制到TEMP目录的数据库复制回原目录,覆盖原Access数据库文件
SQL事务.Commit()
Catch
SQL事务.Commit()
---------------------------------------------------
SQL事务.Commit()
Catch ex As Exception
SQL事务.Rollback("okok")
End Try
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Ok 没问题,有点乱,但实现了,可以将-------------间的代码提出来,写成一个方法或一个类
但现在有一个问题,上面的操作,我想换一下顺序该怎么办
也就是说先执行操作Access,再操作 Sql Server,操作Access时成成,操作Sql Server时出错了,该如
何,
好像也可以解决,再添加一个对复恢Access的回调。
但要还有更多的自定义事务怎么办,而且这些事务又不是一个人开发的怎么办
当然,所有学过面向对象编程的人都会说"定义统一的接口,在同一逻辑关系中处理"
OK,看一下上贴的最后一句话:
"统一的接口,多对象事物协作的前提"
..............下贴..........
准备写
.........(本文正题:)................
二阶段提交
事务回滚
事务回调(事件)
事务挂起
事务回溯
补偿资源
锁定资源-----------------------说明-------------------------
1.上面有不少打错的的字
2.发现VS 2005 有个问题,就是把代码粘到Word 2003中时会有如下情况
a1=a2
变成
a=a
------------
Console.WriteLine("监视点1:初始的[对象值]为:" + wxd.对象值.ToString());
变成
Console.WriteLine("监视点:初始的[对象值]为:" + wxd.对象值.ToString());
-------------
也就是后面的数字没了,不知各位是否有这个问题
所以上面有些代码是在Word里改的,可能有部份错误
3.有些代码是直接在Word中打的,没在VS中打,可能会有单词错误,
4.有时用VB.NET有时用C#,有时直接用语言描绘了,以后会全用C#写,
对"统一的接口"再补充一下,
虽然我们自已实现事物处理在有些时候可能比用System.Transactions类更灵活,
但基于System.Transactions类来自定义事务,可以将所有的自定义事务都放到同一个事物池中
还可以实现自定义事物与某些支持事物的服务器(如Sql Server,BizTalk)协同
///////////////////////////////////////////
完全自定义的事物好像C的指针........
基于System.Transactions类的自定义事务好像NET的GC...........
个人感觉的一种比喻.....与技术方向无关(^_^).................接上上贴............
二阶段提交,事务回滚,事务回调(事件),事务挂起,补偿资源,锁定资源,事务回溯,
概念,高手们都解释过N多遍了,
本人没什么文化,理解有限,
就不说此似是而非的原理,意义,性质,历史,将来,希望,展望,局限,
兄弟没什么文化,也没上过大学(真的),只能用实际应用来举例
1.二阶段提交:
什么意思,就是 "我准备好了吗,你准备好了,是的我也准备好了,提交"
什么是"准备":上例中剪切文件的例子中"将源目录复制到TEMP,将源目录删除"就是准备
什么是"提交": 上例将"TEMP改名就是提交"
准备好了也不见得能提交成功,这也是我前面所说的 “过程操作,瞬间操作”的粒度化分问题
原则上不要在Transactions.IEnlistmentNotification.Commit写里过程操作,
所以前面Transactions解决对象多次被改变后回滚的的例子意义不过是一个演示Transactions工作过程的例子而以
2.回滚,很多解释只是一个恢复原状就过去了。
如何恢复原状,是步骤倒做,状态至回,资源备份,他们不说,咱们一会在后面说
3.事务回调,很多时候子事务的执行要很长一段时间,或者要由其进程或主机完成,这时使用串行可以效率会很低,可以考虑并行,这时就有两个要解决的问题,一是出现错误的子事务通知主事务,二是提到子事物错误通知的主事物通知其他正在执行的子事物。
有点像多线程,其实就是多线程与多进程的操作
4.事务挂起,暂停一个事物,等需要时再接着跑,工作流中常用
是实现补偿资源,事务回溯的前提。
听起来很简单,实现很难,为何这样说,看一下相对论就理解了
5.补偿资源,(^_^)
事物真的是原子性,持久性的吗,
不见得,连原子都不原子了,宇宙都不持久性了,何况事务...............
具体的后面现说,
先举个例子
想到了一个本书上的一个事务的例子(远古人换贝壳),不说了,要真是这样,我想我们现在还是猴子
例子!
一个人在饭店定了20个菜,要请20个朋友
饭店起火了,事物回滚
主人死了,事物回滚
20个朋友都死了,事物回滚
要是有一个朋友有病不能来,或者饭店有一个菜无法做,事物也回滚吗.
补偿一下,事物提交就得了
难道我现在写错了一句,还得把所有贴都删了重写,登一个更证说明不就完了吗
6.锁定资源,高手们举了很多不锁资源的后果,不重复了,
加锁注册,解锁通告,更新通知,死锁这些简单的东西,高手们不屑谈,
我没文化,1+1 这么简单的问题也想显显,咱们后面谈,具体谈,大谈特谈
7.事务回溯
你家是A
你女朋友家是X
你到你女朋友家有
(1)A-B-C-D-E-X
(2)A-B-C-1-2-X
(3)A-B-1-2-3-X
(4)A-1-2-4-5-X
这四条路可以走
当你走第一条路时,E处坏了,不能通过,你是事物回滚,回家
还是退到C处走第(2)条路,还是退到B处走第(3)路
这里我们不谈数据结构,不谈最优算法,不谈链表,二叉树,图
我们要谈回退重试过程中各点的资源管理问题
////////////////////////////////////////////////////////////
----------以上数贴,将事物的来历,目的,意义,应用场景都调侃了一遍
----------对System.Transactions类 的应用场景也有了一个简单交代,
----------下贴正式谈代码实现

浙公网安备 33010602011771号