本问首发于: 不为"事务"而"事务"
背景:
最近在做一个项目,需要用到两个第三方组件:北京莲塘语音组件和CMailSever
前者作为语音聊天室的二次开发组件,后者用于网站的小型邮件系统二次开发组件
需求:
用户在主程序登陆后,无须再次登陆聊天室和邮件系统,即实现一次登陆,全站通行
下面将给出对应方案以及其对事务处理的依赖程度,并给出解决方案
方案:
用户在注册时,同时向聊天室和邮件系统注册一个用户,在用户修改密码时对应修改
聊天室和邮件系统,这样当用户进入聊天室和邮件系统,主系统只需要向其传递用户和密码
即可满足需求
该方案存在的问题及其解决方案:
在用户注册或者修改密码信息时,如果有一个聊天室和邮件系统出现错误,那么用户
将有可能不能登陆其中一个系统,这就提出一个原子事务的问题,为注册和修改密码加上
原子事务处理,C#伪代码如下:
using System.EnterpriseServices ;
[Transaction(TransactionOption.Required)]
public class Users : System.EnterpriseServices.ServicedComponent
{
[AutoComplete]
public static void ModifyPassword( string userName , string olePassword , string newPassword)
{
ModifyMainSystemPassword(userName,newPassword) ; // 修改主程序数据库密码
ModifyVoiceChatPassword(userName,newPassword) ; // 修改聊天室密码
ModifyMailSystemPassword(userName,newPassword) ; // 修改邮件系统密码
}
}
这样就可以实现当有一个系统修改密码出错时都会自动回滚到原来的状态了,OK,问题解决了!
进一步思考:
但事情却没有想象的简单,使用以上代码必须存在一个前提条件:聊天室和邮件系统组件必须支持事务性,
这样才能在应用程序之间共享事务,达到事务提交/回滚的目的
思路暂时中断.......
在CSDN论坛得到思归的帮助: 如何对非事务性组件实现事务处理
即使用所谓的资源补偿器(CRMs),下面简要说说实现的步骤
对于第1,2,4步可以在SDK中搜索System.EnterpriseServices.CompensatingResourceManager命名空间得到更
详细的信息,或则参见 Compensating Resource Managers (CRMs)
这里贴出代码是为了实现的完整性,第3步是我本人扩充的,SDK无此描述
1,实现一个CRM类
using namespace #region using namespace
using System;
using System.IO;
using System.Reflection;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationCrmEnabled]
[assembly: AssemblyKeyFile( " crm.key " )]
#endregion
namespace CrmServer
{
实现代码 #region 实现代码
[Transaction]
// Create a Worker class.
public class CRMWorker:ServicedComponent
{
public void CRMMethod( string fileName, bool bCommit)
{
// Create clerk object.
Clerk clerk = new Clerk( typeof (CRMCompensator), " CRMCompensator " , CompensatorOptions.AllPhases);
clerk.WriteLogRecord(fileName);
clerk.ForceLog();
if (bCommit)
ContextUtil.SetComplete();
else
ContextUtil.SetAbort();
}
}
// Create class derived from Compensator class.
public class CRMCompensator:Compensator
{
bool bBeginPrepareCalled = false ;
bool bPrepareRecordCalled = false ;
bool bBeginCommitCalled = false ;
bool bCommitRecordCalled = false ;
bool bBeginAbortCalled = false ;
bool bAbortRecordCalled = false ;
String _fileName;
public override void BeginPrepare()
{
bBeginPrepareCalled = true ;
}
public override bool PrepareRecord(LogRecord rec)
{
Object o = rec.Record;
_fileName = o.ToString();
bPrepareRecordCalled = true ;
return false ;
}
public override bool EndPrepare()
{
if ( ! bBeginPrepareCalled)
{ return false ;}
if ( ! bPrepareRecordCalled)
{ return false ;}
if (_fileName == null )
{ return false ;}
// This is a Prepare Phase success.
return true ;
}
public override void BeginCommit( bool fRecovery)
{
bBeginCommitCalled = true ;
}
public override bool CommitRecord(LogRecord rec)
{
bCommitRecordCalled = true ;
return true ;
}
public override void EndCommit()
{
if ( ! bBeginCommitCalled)
{ return ;}
if ( ! bCommitRecordCalled)
{ return ;}
if (_fileName == null )
{ return ;}
// This is a Commit Phase success.
}
public override void BeginAbort( bool fRecovery)
{
bBeginAbortCalled = true ;
}
public override bool AbortRecord(LogRecord rec)
{
bAbortRecordCalled = true ;
Object o = rec.Record;
_fileName = o.ToString();
return true ;
}
public override void EndAbort()
{
if ( ! bBeginAbortCalled)
{ return ;}
if ( ! bAbortRecordCalled)
{ return ;} &