ASP.NET Web 服务或通过 .NET Remoting 提供的对象可以使用本地事务根据单个数据库协调工作。如果需要根据多个资源协调工作,它可以使用 .NET 企业服务(也称为 COM+)公布的事务(由 COM+ 管线管理的 DTC 分布式事务)。但要注意的是,ASP.NET Web 服务和 .NET Remoting 管线都不能传播公布的事务,因此两种端点都不可能通过跨进程的调用继承公布的事务。
这不一定是件坏事。一般来讲,公布的事务比本地事务代价要高,而要跨进程传播公布的事务,则代价会更高。如果您确实需要这种功能,简单的解决方案是在 .NET 企业服务服务器应用程序中展开一个从 System.EnterpriseServices.ServicedComponent 中衍生的类(有关详细信息,请参阅 COM+ Integration: How .NET Enterprise Services Can Help You Build Distributed Applications [英文])。对该类对象的跨进程调用将使用 DCOM 进行处理,以确保正确传播事务环境。较难的解决方案是使用底层的 API,手动传播分布的事务。
值得注意的是,传统的分布式事务模型一般不适用于松散耦合的 Web 服务。基于补偿事务的模型(即,撤消其他事务所提交工作的事务)更有意义,因为其隔离约束条件并不是很严格。在包括 Microsoft 的 Web 服务供应商中有一种普遍的说法,即 Web 服务空间需要的事务模型越灵活,该空间中进行的工作越多。等到定义出 Web 服务事务的标准方法时,您就可以根据情况使用本地或公布的事务实现自己的补偿架构了。
图 1 显示了一个简单的 3 层体系结构,它带有 Web 服务器领域,支持一系列不同的客户端。所有服务器端的代码都在 ASP.NET 辅助进程 aspnet_wp.exe 中执行。这三种不同类型的客户端可以使用 HTTP 访问服务器领域。基于浏览器的客户端调用 ASP.NET Web 页面;多客户端(如 Windows 窗体应用程序、Microsoft® Visual Basic® 6 应用程序)和其他 Web 服务使用 ASP.NET Web 服务;根据需要,.NET 多客户端(如 Windows 窗体应用程序)和 Web 服务使用 ASP.NET Web 服务,或使用 HTTP 通道和二进制格式化程序的 .NET Remoting(假定它不在沙箱中)。
图 2:使用 ASP.NET 的 n 层体系结构
一些非常大的应用程序使用一套辅助计算机从 Web 服务器的外层分担工作。这种体系结构如图 2 所示。请注意,在这种情况下,第二层也通过 ASP.NET 提供功能。
小结 虽然 .NET Remoting 基础结构和 ASP.NET Web 服务都可以进行跨进程通信,但每种设计适用于不同的用户。ASP.NET Web 服务的编程模型很简单,使用范围很广。.NET Remoting 的编程模型较复杂,使用范围较窄。请务必了解这两种技术的工作原理,并选择适合您应用程序的技术。在任意一种情况下,都要使用 IIS 和 ASP.NET 管理进程生命周期,并提供一般的安全性。
我寫的一個實例 using System; using System.EnterpriseServices; using System.Data; using System.Data.SqlClient; using System.Data.OleDb; using System.Runtime.InteropServices; using System.Collections; using ItemInfo;namespace MyControls.ComServices.Transaction { public interface IBusiness { DataSet QueryShop(); DataSet QueryBank(); void Buy(string sCardNO , ArrayList Arr); void ReduceCreditAmount(string sCardNO , int nCost , OleDbCommand cmd); void ReduceShopStock(string sWareNO , int nCount , OleDbCommand cmd); } [Transaction(TransactionOption.Required)] [ObjectPooling(Enabled = true , MinPoolSize = 1 , MaxPoolSize = 50 , CreationTimeout = 60)] public class BusinessClass : ServicedComponent,IBusiness { public BusinessClass() { } //查詢商店 public DataSet QueryShop() { DataSet oDs = new DataSet(); string sConnShop = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Shop;User Id=sa;"; OleDbConnection ConnShop = new OleDbConnection(sConnShop); string sCmd = "SELECT WARENO , NUM FROM WARE"; OleDbDataAdapter OleAda = new OleDbDataAdapter(sCmd , ConnShop); ConnShop.Open(); OleAda.Fill(oDs); ConnShop.Close(); return oDs; } //查詢銀行 public DataSet QueryBank() { DataSet oDs = new DataSet(); string sConnBank = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Bank;User Id=sa;"; OleDbConnection ConnBank = new OleDbConnection(sConnBank); string sCmd = "SELECT CREDIT_NO , AMOUNT FROM CREDIT_INFO"; OleDbDataAdapter OleAda = new OleDbDataAdapter(sCmd , ConnBank); ConnBank.Open(); OleAda.Fill(oDs); ConnBank.Close(); return oDs; } //購物 public void Buy(string sCardNO , ArrayList Arr) { //****************構建連接****************** string sConnShop = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Shop;User Id=sa;"; string sConnBank = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Bank;User Id=sa;"; OleDbConnection ConnShop = new OleDbConnection(sConnShop); OleDbConnection ConnBank = new OleDbConnection(sConnBank); OleDbCommand CmdShop = new OleDbCommand(); CmdShop.Connection = ConnShop; OleDbCommand CmdBank = new OleDbCommand(); CmdBank.Connection = ConnBank; try { //****************打開連接******************* ConnBank.Open(); ConnShop.Open(); //****************************************** if(!ContextUtil.IsInTransaction) { throw new Exception("Not in trsaction!"); } //************操作資料庫Shop****************** int nCost = 0 ; for(int i = 0 ; i < Arr.Count ; i++) { Item item = (Item)Arr[i]; string sWareNO = item.sNO; int nCount = item.nCount; ReduceShopStock(sWareNO,nCount,CmdShop); nCost += nCount; } //************操作資料庫Bank****************** ReduceCreditAmount(sCardNO , nCost , CmdBank); //*******************提交******************** ContextUtil.SetComplete(); } catch(Exception Ex) { //****************回滾************************ ContextUtil.SetAbort(); throw new Exception(Ex.Message); } finally { ConnBank.Close(); ConnShop.Close(); } } //減少庫存 public void ReduceShopStock(string sWareNO , int nCount , OleDbCommand cmd) { string sCmd = "SELECT NUM FROM WARE WHERE WARENO ='" + sWareNO + "'"; OleDbDataAdapter ada= new OleDbDataAdapter(sCmd , cmd.Connection); DataSet oDs = new DataSet(); ada.Fill(oDs); int nStock = 0 ; if(oDs.Tables[0].Rows.Count > 0) { nStock = int.Parse(oDs.Tables[0].Rows[0][0].ToString().Trim()); } if(nCount > nStock) { throw new Exception(sWareNO + " didn't have enough stock[" + nStock +"]"); } sCmd = "UPDATE WARE SET NUM =" + Convert.ToString(nStock - nCount) + " WHERE WARENO ='" + sWareNO + "'"; cmd.CommandText = sCmd; if(cmd.ExecuteNonQuery()<=0) { throw new Exception("Can not found [" + sWareNO + "]"); } } //減少金額 public void ReduceCreditAmount(string sCardNO , int nCost , OleDbCommand cmd) { string sCmd = "SELECT AMOUNT FROM CREDIT_INFO WHERE CREDIT_NO ='" + sCardNO + "'"; OleDbDataAdapter ada = new OleDbDataAdapter(sCmd , cmd.Connection); DataSet oDs = new DataSet(); ada.Fill(oDs); int nAmount = 0; if(oDs.Tables[0].Rows.Count > 0) { nAmount = int.Parse(oDs.Tables[0].Rows[0][0].ToString().Trim()); } if(nAmount < nCost) { throw new Exception("Sorry! You don't have enough money!"); } sCmd = "UPDATE CREDIT_INFO SET AMOUNT = " + Convert.ToString(nAmount - nCost) + " WHERE CREDIT_NO ='" + sCardNO + "'"; cmd.CommandText = sCmd; if(cmd.ExecuteNonQuery()<=0) { throw new Exception("Can not found [" + sCardNO +"]"); } } } }
using System;namespace ItemInfo { /// <summary> /// Class1 的摘要描述。 /// </summary> [Serializable] public class Item { public Item() { } public string sNO; public int nCount; } }
这不一定是件坏事。一般来讲,公布的事务比本地事务代价要高,而要跨进程传播公布的事务,则代价会更高。如果您确实需要这种功能,简单的解决方案是在 .NET 企业服务服务器应用程序中展开一个从 System.EnterpriseServices.ServicedComponent 中衍生的类(有关详细信息,请参阅 COM+ Integration: How .NET Enterprise Services Can Help You Build Distributed Applications [英文])。对该类对象的跨进程调用将使用 DCOM 进行处理,以确保正确传播事务环境。较难的解决方案是使用底层的 API,手动传播分布的事务。
值得注意的是,传统的分布式事务模型一般不适用于松散耦合的 Web 服务。基于补偿事务的模型(即,撤消其他事务所提交工作的事务)更有意义,因为其隔离约束条件并不是很严格。在包括 Microsoft 的 Web 服务供应商中有一种普遍的说法,即 Web 服务空间需要的事务模型越灵活,该空间中进行的工作越多。等到定义出 Web 服务事务的标准方法时,您就可以根据情况使用本地或公布的事务实现自己的补偿架构了。
选择体系结构
如果您正在设计一个基于 .NET 的分布式应用程序,则需要考虑本文中讨论的所有问题,并对系统体系结构的应有结果得出一些结论。一般来讲,这比您想像的要容易些。但总有一些特殊的情况需要其他的方法,以下是您可以进行的某些一般假设,可为您简化情况。
首先,在默认情况下使用 ASP.NET Web 服务。它们的执行和使用都很简单,可以为客户端平台提供尽可能宽的使用范围,而且可以从默认安全性策略下沙箱中运行的代码中调用 ASP.NET Web 服务客户端代理代码。
如果要使用较传统的带有 CLR 类型保真度的分布式对象模型,不需要与其他平台进行互操作,而且由您控制客户端和服务器的配置,请考虑使用 .NET Remoting。如果您选择 .NET Remoting,最好使 HTTP 通道与 IIS 和 ASP.NET 集成,否则,必须建立自己的进程生命周期管理和安全性基础结构。假定 .NET Remoting 需要 .NET 客户端,使用二进制格式化程序而不是 SOAP 格式化程序是很有意义的;互操作性将不成问题,而且性能将显著提高。
最后,如果需要公布的事务,请使用企业服务 (COM+)。如果您执行 ServicedComponents,则出于性能方面的考虑,默认情况下它们将部署在库应用程序中。如果它们需要在远程计算机上运行,则将它们部署在服务器应用程序中。(如果您需要执行不同的进程安全性令牌 [而不是 aspnet_wp.exe 使用的令牌] 的代码,即使在相同的计算机上,可能也要考虑使用 COM+ 服务器应用程序。)
以下是三个基于这些理念的公共体系结构。
图 1:简单的 3 层体系结构
图 1 显示了一个简单的 3 层体系结构,它带有 Web 服务器领域,支持一系列不同的客户端。所有服务器端的代码都在 ASP.NET 辅助进程 aspnet_wp.exe 中执行。这三种不同类型的客户端可以使用 HTTP 访问服务器领域。基于浏览器的客户端调用 ASP.NET Web 页面;多客户端(如 Windows 窗体应用程序、Microsoft® Visual Basic® 6 应用程序)和其他 Web 服务使用 ASP.NET Web 服务;根据需要,.NET 多客户端(如 Windows 窗体应用程序)和 Web 服务使用 ASP.NET Web 服务,或使用 HTTP 通道和二进制格式化程序的 .NET Remoting(假定它不在沙箱中)。
图 2:使用 ASP.NET 的 n 层体系结构
一些非常大的应用程序使用一套辅助计算机从 Web 服务器的外层分担工作。这种体系结构如图 2 所示。请注意,在这种情况下,第二层也通过 ASP.NET 提供功能。
图 3:使用企业服务 (COM+) 的 n 层体系结构
图 3 显示此体系结构的另一种版本,其第二层使用在 COM+ 中部署的 ServicedComponents 提供功能。
显然,这些并不是 .NET Framework 所支持的所有可能的体系结构。但是,它为您设计自己的系统提供了适当的基础。
小结
虽然 .NET Remoting 基础结构和 ASP.NET Web 服务都可以进行跨进程通信,但每种设计适用于不同的用户。ASP.NET Web 服务的编程模型很简单,使用范围很广。.NET Remoting 的编程模型较复杂,使用范围较窄。请务必了解这两种技术的工作原理,并选择适合您应用程序的技术。在任意一种情况下,都要使用 IIS 和 ASP.NET 管理进程生命周期,并提供一般的安全性。
using System;
using System.EnterpriseServices;
using System.Data;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Runtime.InteropServices;
using System.Collections;
using ItemInfo;namespace MyControls.ComServices.Transaction
{
public interface IBusiness
{
DataSet QueryShop();
DataSet QueryBank();
void Buy(string sCardNO , ArrayList Arr);
void ReduceCreditAmount(string sCardNO , int nCost , OleDbCommand cmd);
void ReduceShopStock(string sWareNO , int nCount , OleDbCommand cmd);
} [Transaction(TransactionOption.Required)]
[ObjectPooling(Enabled = true , MinPoolSize = 1 , MaxPoolSize = 50 , CreationTimeout = 60)]
public class BusinessClass : ServicedComponent,IBusiness
{
public BusinessClass()
{
}
//查詢商店
public DataSet QueryShop()
{
DataSet oDs = new DataSet();
string sConnShop = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Shop;User Id=sa;";
OleDbConnection ConnShop = new OleDbConnection(sConnShop);
string sCmd = "SELECT WARENO , NUM FROM WARE";
OleDbDataAdapter OleAda = new OleDbDataAdapter(sCmd , ConnShop);
ConnShop.Open();
OleAda.Fill(oDs);
ConnShop.Close();
return oDs;
}
//查詢銀行
public DataSet QueryBank()
{
DataSet oDs = new DataSet();
string sConnBank = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Bank;User Id=sa;";
OleDbConnection ConnBank = new OleDbConnection(sConnBank);
string sCmd = "SELECT CREDIT_NO , AMOUNT FROM CREDIT_INFO";
OleDbDataAdapter OleAda = new OleDbDataAdapter(sCmd , ConnBank);
ConnBank.Open();
OleAda.Fill(oDs);
ConnBank.Close();
return oDs;
}
//購物
public void Buy(string sCardNO , ArrayList Arr)
{
//****************構建連接******************
string sConnShop = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Shop;User Id=sa;";
string sConnBank = "Provider=sqloledb;Data Source=JANE;Initial Catalog=Bank;User Id=sa;";
OleDbConnection ConnShop = new OleDbConnection(sConnShop);
OleDbConnection ConnBank = new OleDbConnection(sConnBank);
OleDbCommand CmdShop = new OleDbCommand();
CmdShop.Connection = ConnShop;
OleDbCommand CmdBank = new OleDbCommand();
CmdBank.Connection = ConnBank;
try
{
//****************打開連接*******************
ConnBank.Open();
ConnShop.Open();
//******************************************
if(!ContextUtil.IsInTransaction)
{
throw new Exception("Not in trsaction!");
}
//************操作資料庫Shop******************
int nCost = 0 ;
for(int i = 0 ; i < Arr.Count ; i++)
{
Item item = (Item)Arr[i];
string sWareNO = item.sNO;
int nCount = item.nCount;
ReduceShopStock(sWareNO,nCount,CmdShop);
nCost += nCount;
}
//************操作資料庫Bank******************
ReduceCreditAmount(sCardNO , nCost , CmdBank);
//*******************提交********************
ContextUtil.SetComplete();
}
catch(Exception Ex)
{
//****************回滾************************
ContextUtil.SetAbort();
throw new Exception(Ex.Message);
}
finally
{
ConnBank.Close();
ConnShop.Close();
}
}
//減少庫存
public void ReduceShopStock(string sWareNO , int nCount , OleDbCommand cmd)
{
string sCmd = "SELECT NUM FROM WARE WHERE WARENO ='" + sWareNO + "'";
OleDbDataAdapter ada= new OleDbDataAdapter(sCmd , cmd.Connection);
DataSet oDs = new DataSet();
ada.Fill(oDs);
int nStock = 0 ;
if(oDs.Tables[0].Rows.Count > 0)
{
nStock = int.Parse(oDs.Tables[0].Rows[0][0].ToString().Trim());
}
if(nCount > nStock)
{
throw new Exception(sWareNO + " didn't have enough stock[" + nStock +"]");
}
sCmd = "UPDATE WARE SET NUM =" + Convert.ToString(nStock - nCount) + " WHERE WARENO ='" + sWareNO + "'";
cmd.CommandText = sCmd;
if(cmd.ExecuteNonQuery()<=0)
{
throw new Exception("Can not found [" + sWareNO + "]");
}
}
//減少金額
public void ReduceCreditAmount(string sCardNO , int nCost , OleDbCommand cmd)
{
string sCmd = "SELECT AMOUNT FROM CREDIT_INFO WHERE CREDIT_NO ='" + sCardNO + "'";
OleDbDataAdapter ada = new OleDbDataAdapter(sCmd , cmd.Connection);
DataSet oDs = new DataSet();
ada.Fill(oDs);
int nAmount = 0;
if(oDs.Tables[0].Rows.Count > 0)
{
nAmount = int.Parse(oDs.Tables[0].Rows[0][0].ToString().Trim());
}
if(nAmount < nCost)
{
throw new Exception("Sorry! You don't have enough money!");
}
sCmd = "UPDATE CREDIT_INFO SET AMOUNT = " + Convert.ToString(nAmount - nCost) + " WHERE CREDIT_NO ='" + sCardNO + "'";
cmd.CommandText = sCmd;
if(cmd.ExecuteNonQuery()<=0)
{
throw new Exception("Can not found [" + sCardNO +"]");
}
}
}
}
{
/// <summary>
/// Class1 的摘要描述。
/// </summary>
[Serializable]
public class Item
{
public Item()
{
}
public string sNO;
public int nCount;
}
}
[email protected]