如何在 Visual C# 组件中使用 COM+ 事务

本文转载自:http://support.microsoft.com/kb/816141/zh-cn

本文分步介绍了如何在 Visual C# 类中使用 COM+(组件服务)事务。一组数据库操作被视为一个事务单元。要么所有的操作都成功;要么如果其中一个操作失败,则整个事务失败。对于后一种情况,所尝试的任何数据库操作都不会发布到基础数据库中。 


要求

下面几项介绍了推荐使用的硬件、软件、网络基础结构、技能和知识以及必需的 Service Pack:
  • Microsoft Windows 2000 Server SP1
  • Microsoft Internet 信息服务 (IIS) 4.0 或更高版本
  • Microsoft Internet Explorer 5.0、5.5 或 6.0 版
本文假定您已熟悉以下主题:
  • 事务的概念和处理
  • COM+(组件服务)

COM+ 事务服务

您可以使用 Microsoft .NET Framework 中的  System.EnterpriseServices  命名空间来实现事务处理。要访问 COM+ 事务服务,需创建一个类。为此,请按照下列步骤操作:
  1. 启动 Visual Studio .NET 或 Visual Studio 2005。
  2. 在“文件”菜单上,指向“新建”,然后单击“项目”。
  3. 单击“项目类型”下的“Visual C# 项目”,然后单击“模板”下的“类库”。将该项目命名为 prjEnterprise

    注意:在 Visual Studio 2005 中,请单击“项目类型”下的“Visual C#”,然后单击“模板”下的“类库”。将该项目命名为prjEnterprise
  4. 默认情况下将创建 Class1。
  5. 在解决方案资源管理器中,右键单击“引用”,然后单击“添加引用”。
  6. 将出现“添加引用”对话框。在“.NET”选项卡的“组件名称”下,双击“System.EnterpriseServices”。
  7. 确保“System.EnterpriseServices”显示在“选定的组件”下。单击“确定”。
  8. 将以下代码添加到 Class1.cs 文件中的其他任何语句之前:
    using System.EnterpriseServices;
    using System.Data.SqlClient;
  9. 向 Class1.cs 文件中添加一个名为 clsES 的新类。
  10. 要使用 COM+ 事务服务,您的类 (clsES) 必须按如下方式从 ServicedComponent 继承功能:
    public class clsES : ServicedComponent
  11. 按如下方式使用一个 Transaction 属性来指定该类的事务支持级别:
    [Transaction(TransactionOption.Required)]public class clsES : ServicedComponent
  12. 在 clsES 类中创建一个方法,然后将其命名为 dbAccess,该方法接收四个整数作为输入参数。前两个参数提供一个产品 ID 和这种产品的订货数量;后两个参数提供一个产品 ID 和这种产品的库存数量。该方法对这些指定的产品 ID 执行一组数据库操作,这组数据库操作将被视为一个事务:
    void dbAccess(int pID1,int onOrder, int pID2, int inStock)
  13. 在 dbAccess 方法中,创建一个用于罗斯文数据库的 SQL 连接对象,然后打开该连接。数据库操作是使用以下数据库进行的:

    注意:不要忘记更改下面的连接字符串参数以反映您的 SQL Server 服务器的正确值。
    SqlConnection Conn = new SqlConnection("user id=<username>;password=<strong password>;Initial Catalog=northwind;Data Source=2E124\\SQL;");
    				Conn.Open();
    
  14. 设置一个 try 块以捕获在数据库处理过程中可能出现的任何异常。您必须捕获这些异常来终止事务。try 块包括两个数据库操作,每个操作更新指定的 Products 表记录中的一个不同字段。
     try { 
    
  15. 对 Products 表执行第一个更新。按照前两个输入参数的指定,使用 onOrder 值更新具有指定 ID 的产品的 UnitsonOrder字段。使用下面的 SQL 命令运行这个 SQL 更新:
    SqlCommand sqlCommand = new SqlCommand("UPDATE myProducts SET UnitsonOrder = " + onOrder + " WHERE productID = " + pID1, Conn);
    				sqlCommand.ExecuteNonQuery();
  16. 对 Products 表执行另一个更新。按照第三个和第四个输入参数的指定,使用 inStock 值更新具有指定 ID 的产品的UnitsinStock 字段。使用下面的 SQL 命令运行这个 SQL 更新:
    sqlCommand.CommandText = "UPDATE myProducts SET UnitsinStock = " + inStock + " WHERE productID = " + pID2;
    				sqlCommand.ExecuteNonQuery();
    
  17. 因为这些更新是 COM+ 事务的一部分,所以它们作为一个单元提交。如果没有引发错误,将使用System.EnterpriseServices 命名空间中的 contextUtil 类的 setComplete 方法提交该事务(在本例中是两个更新):
    ContextUtil.SetComplete();
  18. 与罗斯文数据库的连接关闭:
    Conn.Close();
    }
  19. 您必须捕获运行 SQL 命令时发生的任何异常以便能够终止整个事务:
    catch(Exception e){ 
  20. 使用 System.EnterpriseServices 命名空间中的 contextUtil 类的 setAbort 方法终止整个事务。如果第一个更新成功但第二个更新失败,则两个更新都不会发布到 Products 表。捕获到的异常将抛给调用者,表明事务已失败:
    ContextUtil.SetAbort();
    				throw e;
    }
  21. 为使该组件正确运行,该组件必须有一个强名称。生成一个强名称,然后使用该强名称对程序集进行签名。为此,请按照下列步骤操作:
    1. 在 Visual Studio .NET 命令提示符处,键入 sn.exe -k snEnterprise.snk 以创建一个密钥文件。有关使用强名称对程序集进行签名的更多信息,请参见 .NET Framework SDK 文档。
    2. 将 snEnterprise.snk 复制到您的项目文件夹中。
    3. 在 AssemblyInfo.vc 中,将以下代码行添加到其他程序集属性语句之前或之后:
      [assembly: AssemblyKeyFileAttribute("..\\..\\snEnterprise.snk")]		
    4. 进行保存,然后生成您的项目。

完整代码列表



注意 :不要忘记更改下面的连接字符串参数以反映您的 SQL Server 服务器的正确值。
using System;
using System.Data;
using System.Data.SqlTypes;
using System.Data.Common;
using System.EnterpriseServices;
using System.Data.SqlClient;

namespace prjEnterprise
{
	
	[Transaction(TransactionOption.Required)]public class clsES:ServicedComponent
	{
		public SqlConnection Conn;

		public void dbAccess(int pID1, int onOrder, int pID2, int inStock)
		{
			try
			{			
				SqlConnection Conn = new SqlConnection("user id=<username>;password=<strong password>;Initial Catalog=northwind;Data Source=2E124\\SQL;");
				Conn.Open();
				
SqlCommand sqlCommand = new SqlCommand("UPDATE myProducts SET UnitsonOrder = " + onOrder + " WHERE productID = " + pID1, Conn);
				sqlCommand.ExecuteNonQuery();
				
				sqlCommand.CommandText = "UPDATE myProducts SET UnitsinStock = " + inStock + " WHERE productID = " + pID2;
				sqlCommand.ExecuteNonQuery();

				ContextUtil.SetComplete();
				Conn.Close();
			}
			catch(Exception e)
			{
				ContextUtil.SetAbort();
			
				throw e;
			}
			finally
			{

			}
		}
	}	
}

确认它可以使用

要测试这个代码,需要创建一个使用 clsES 项目的控制台应用程序。一种情形是:事务成功,指定产品的  onorder  和  instock  字段得到了更新。另一种情形是:对一个指定产品的  onOrder  字段的更新成功了,但对另一个指定产品的  inStock  字段的更新失败了(因为在 Products 表中不存在指定的产品编号)。这会导致事务失败,该事务将被忽略。
  1. 在 Visual Studio .NET 或 Visual Studio 2005 中,指向“文件”菜单上的“新建”,然后单击“项目”。
  2. 单击“项目类型”下的“Visual C# 项目”,然后单击“模板”下的“控制台应用程序”。

    注意:在 Visual Studio 2005 中,请单击“项目类型”下的“Visual C#”,然后单击“模板”下的“控制台应用程序”。
  3. 在“名称”文本框中,键入 testES。确保已选中了“添入解决方案”选项。
  4. 单击“确定”将该项目添加到解决方案中。
  5. 要使 testES 测试 clsES,您必须添加一个引用。在解决方案资源管理器中,右键单击 testES(您刚才添加的)下的“引用”,然后单击“添加引用”。
  6. 将出现“添加引用”对话框。在“项目”选项卡上,双击“prjEnterprise”。
  7. 在“选定的组件”下显示一个引用。单击“确定”向项目中添加该引用。
  8. 在项目中添加对 System.EnterpriseServices 库的引用。在解决方案资源管理器中,右键单击“引用”,然后单击“添加引用”。
  9. 将出现“添加引用”对话框。在“.NET”选项卡的“组件名称”下,双击“System.EnterpriseServices”。
  10. 确保“System.EnterpriseServices”显示在“选定的组件”下。单击“确定”。
  11. 右键单击控制台应用程序 (testES),然后单击“设为启动项目”。
  12. 将以下源代码粘贴到 Class1 类的 Main 函数中:
    	prjEnterprise.clsES myTest = new prjEnterprise.clsES();
    
    				try 
    				{
    					myTest.dbAccess(1, 777, 2, 888);
    					Console.WriteLine("TRANSACTION ONE -- SUCCESS");
    
    					myTest.dbAccess(1, 5, 2, -20);
    					Console.WriteLine("TRANSACTION TWO -- SUCCESS");
    				}
    				catch (Exception e)
    				{
    					Console.WriteLine("TRANSACTION FAILURE");
    				}
  13. 按 F5 以运行测试代码。

    在代码的步骤 7 中,对 dbAccess 的第一个调用成功完成。产品 1 和产品 2 均在 Products 表中。产品 1 的 onOrder 字段更新为 777,产品 2 的 inStock 字段更新为 888。因为这个事务成功完成,所以您将在输出窗口中收到以下消息
    事务 1 - 成功


    对 dbAccess 的第二个调用失败。因此,不会将 dbAccess 中的对 Products 表的任何更新语句发布到数据库。虽然产品 1 本来可以将它的 onOrder 字段更新为 5,但是产品 2 不能将它的 inStock 字段设为 -20。(因为 Products 表定义中定义的一个约束条件规定 inStock 不允许为负数)。

    因此,对 dbAccess 的这个调用失败了,从而整个事务也随之失败。Products 表将保持调用 dbAccess 之前的状态。catch语句处理来自 dbAccess 的事务失败通知,您将在输出窗口中收到以下错误消息:
    TRANSACTION FAILURE
  14. 使用 SQL Server 企业管理器检查罗斯文的 Products 表的内容。查看产品 1,onOrder 字段的值为 777。查看产品 2,instock 字段的值为 888。因此,对 dbAccess 的第二个调用(此调用本会使这些字段具有不同的值)失败了。

疑难解答

  • 确保使用 COM+ 服务的所有项目都有一个强名称。
  • 使用 COM+ 服务的所有类都必须继承服务组件。服务组件位于 System.EnterpriseServices 命名空间中。
  • 进行调试时,事务在提交或终止前可能会超时。要避免出现超时,请在事务属性中使用一个超时属性。在下面的示例中,在完成任何事务时,关联的方法在超时前都有 1,200 秒的执行时间:
    [Transaction(TransactionOption.Required,timeout:=1200)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
绝对能看能用的C#代码 using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace NetAddressCollector { public class CDataAccess : IDataAccess { #region IDataAccess 成员 private ArrayList _classList; public ArrayList ClassList { get { return _classList; } } private ArrayList _addressList; public ArrayList AddressList { get { return _addressList; } } private bool _isDirty = false; public bool IsDirty { get { return _isDirty; } } public void LoadData() { _classList = new ArrayList(); _addressList = new ArrayList(); string content = CFileOperation.ReadFile(CConst.DATAFILE); if (content.Length < 1) return; string[] ss1 = content.Split(new string[] { CConst.SPLITOR_CLASS_HTTP }, StringSplitOptions.RemoveEmptyEntries); string classString = ss1[0]; string httpString = ss1[1]; string[] ss2 = classString.Split(new string[] { CConst.SPLITOR_CLASS_ITEM }, StringSplitOptions.RemoveEmptyEntries); foreach (string s in ss2) _classList.Add(new CClass(s)); string[] ss3 = httpString.Split(new string[] { CConst.SPLITOR_HTTP_ITEM }, StringSplitOptions.RemoveEmptyEntries); foreach (string s in ss3) { string[] ss4 = s.Split(new string[] { CConst.SPLITOR_HTTP_DETAIL }, StringSplitOptions.RemoveEmptyEntries); foreach (CClass c in _classList) { if (c.Name == ss4[0]) { _addressList.Add(new CAddress(c, ss4[1], ss4[2], ss4[3])); break; } } } _isDirty = false; } public void SaveData() { StringBuilder sbClass = new StringBuilder(); foreach (CClass c in _classList) sbClass.Append(string.Format("{0}{1}", CConst.SPLITOR_CLASS_ITEM, c.Name)); StringBuilder sbContent = sbClass.Append(CConst.SPLITOR_CLASS_HTTP); StringBuilder sbHttp = new StringBuilder(); foreach (CAddress http in _addressList) sbHttp.Append(string.Format("{0}{1}", CConst.SPLITOR_HTTP_ITEM, http.ToString())); sbContent.Append(sbHttp); CFileOperation.WriteFile(CConst.DATAFILE, sbContent.ToString()); _isDirty = false; } public bool ExistClass(CClass httpClass) { foreach (CClass c in _classList) { if (c.Name == httpClass.Name) return true; } return false; } public bool ExistHttp(CAddress http) { foreach (CAddress addr in _addressList) { if (addr.Name == http.Name && addr.Name == http.Http) return true; } return false; } public void AddClass(string className) { CClass c = new CClass(className); AddClass(c); } public void AddClass(CClass httpClass) { _classList.Add(httpClass); _isDirty = true; } public void RemoveClass(CClass httpClass) { for (int i = _addressList.Count - 1; i >= 0; i--) { CAddress http = (CAddress)_addressList[i]; if (http.HttpClass.Name == httpClass.Name) RemoveHttp(http); } _classList.Remove(httpClass); _isDirty = true; } public void AddHttp(CClass httpClass, string name, string http, string remark) { CAddress addr = new CAddress(httpClass, name, http, remark); AddHttp(addr); } public void AddHttp(CAddress http) { _addressList.Add(http); _isDirty = true; } public void RemoveHttp(CAddress http) { _addressList.Remove(http); _isDirty = true; } #endregion } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值