本文转载自: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+ 事务服务,需创建一个类。为此,请按照下列步骤操作:- 启动 Visual Studio .NET 或 Visual Studio 2005。
- 在“文件”菜单上,指向“新建”,然后单击“项目”。
- 单击“项目类型”下的“Visual C# 项目”,然后单击“模板”下的“类库”。将该项目命名为 prjEnterprise。
注意:在 Visual Studio 2005 中,请单击“项目类型”下的“Visual C#”,然后单击“模板”下的“类库”。将该项目命名为prjEnterprise。 - 默认情况下将创建 Class1。
- 在解决方案资源管理器中,右键单击“引用”,然后单击“添加引用”。
- 将出现“添加引用”对话框。在“.NET”选项卡的“组件名称”下,双击“System.EnterpriseServices”。
- 确保“System.EnterpriseServices”显示在“选定的组件”下。单击“确定”。
- 将以下代码添加到 Class1.cs 文件中的其他任何语句之前:
using System.EnterpriseServices; using System.Data.SqlClient;
- 向 Class1.cs 文件中添加一个名为 clsES 的新类。
- 要使用 COM+ 事务服务,您的类 (clsES) 必须按如下方式从 ServicedComponent 继承功能:
public class clsES : ServicedComponent
- 按如下方式使用一个 Transaction 属性来指定该类的事务支持级别:
[Transaction(TransactionOption.Required)]public class clsES : ServicedComponent
- 在 clsES 类中创建一个方法,然后将其命名为 dbAccess,该方法接收四个整数作为输入参数。前两个参数提供一个产品 ID 和这种产品的订货数量;后两个参数提供一个产品 ID 和这种产品的库存数量。该方法对这些指定的产品 ID 执行一组数据库操作,这组数据库操作将被视为一个事务:
void dbAccess(int pID1,int onOrder, int pID2, int inStock)
- 在 dbAccess 方法中,创建一个用于罗斯文数据库的 SQL 连接对象,然后打开该连接。数据库操作是使用以下数据库进行的:
注意:不要忘记更改下面的连接字符串参数以反映您的 SQL Server 服务器的正确值。
SqlConnection Conn = new SqlConnection("user id=<username>;password=<strong password>;Initial Catalog=northwind;Data Source=2E124\\SQL;"); Conn.Open();
- 设置一个 try 块以捕获在数据库处理过程中可能出现的任何异常。您必须捕获这些异常来终止事务。try 块包括两个数据库操作,每个操作更新指定的 Products 表记录中的一个不同字段。
try {
- 对 Products 表执行第一个更新。按照前两个输入参数的指定,使用 onOrder 值更新具有指定 ID 的产品的 UnitsonOrder字段。使用下面的 SQL 命令运行这个 SQL 更新:
SqlCommand sqlCommand = new SqlCommand("UPDATE myProducts SET UnitsonOrder = " + onOrder + " WHERE productID = " + pID1, Conn); sqlCommand.ExecuteNonQuery();
- 对 Products 表执行另一个更新。按照第三个和第四个输入参数的指定,使用 inStock 值更新具有指定 ID 的产品的UnitsinStock 字段。使用下面的 SQL 命令运行这个 SQL 更新:
sqlCommand.CommandText = "UPDATE myProducts SET UnitsinStock = " + inStock + " WHERE productID = " + pID2; sqlCommand.ExecuteNonQuery();
- 因为这些更新是 COM+ 事务的一部分,所以它们作为一个单元提交。如果没有引发错误,将使用System.EnterpriseServices 命名空间中的 contextUtil 类的 setComplete 方法提交该事务(在本例中是两个更新):
ContextUtil.SetComplete();
- 与罗斯文数据库的连接关闭:
Conn.Close(); }
- 您必须捕获运行 SQL 命令时发生的任何异常以便能够终止整个事务:
catch(Exception e){
- 使用 System.EnterpriseServices 命名空间中的 contextUtil 类的 setAbort 方法终止整个事务。如果第一个更新成功但第二个更新失败,则两个更新都不会发布到 Products 表。捕获到的异常将抛给调用者,表明事务已失败:
ContextUtil.SetAbort(); throw e; }
- 为使该组件正确运行,该组件必须有一个强名称。生成一个强名称,然后使用该强名称对程序集进行签名。为此,请按照下列步骤操作:
- 在 Visual Studio .NET 命令提示符处,键入 sn.exe -k snEnterprise.snk 以创建一个密钥文件。有关使用强名称对程序集进行签名的更多信息,请参见 .NET Framework SDK 文档。
- 将 snEnterprise.snk 复制到您的项目文件夹中。
- 在 AssemblyInfo.vc 中,将以下代码行添加到其他程序集属性语句之前或之后:
[assembly: AssemblyKeyFileAttribute("..\\..\\snEnterprise.snk")]
- 进行保存,然后生成您的项目。
完整代码列表
注意 :不要忘记更改下面的连接字符串参数以反映您的 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 表中不存在指定的产品编号)。这会导致事务失败,该事务将被忽略。- 在 Visual Studio .NET 或 Visual Studio 2005 中,指向“文件”菜单上的“新建”,然后单击“项目”。
- 单击“项目类型”下的“Visual C# 项目”,然后单击“模板”下的“控制台应用程序”。
注意:在 Visual Studio 2005 中,请单击“项目类型”下的“Visual C#”,然后单击“模板”下的“控制台应用程序”。 - 在“名称”文本框中,键入 testES。确保已选中了“添入解决方案”选项。
- 单击“确定”将该项目添加到解决方案中。
- 要使 testES 测试 clsES,您必须添加一个引用。在解决方案资源管理器中,右键单击 testES(您刚才添加的)下的“引用”,然后单击“添加引用”。
- 将出现“添加引用”对话框。在“项目”选项卡上,双击“prjEnterprise”。
- 在“选定的组件”下显示一个引用。单击“确定”向项目中添加该引用。
- 在项目中添加对 System.EnterpriseServices 库的引用。在解决方案资源管理器中,右键单击“引用”,然后单击“添加引用”。
- 将出现“添加引用”对话框。在“.NET”选项卡的“组件名称”下,双击“System.EnterpriseServices”。
- 确保“System.EnterpriseServices”显示在“选定的组件”下。单击“确定”。
- 右键单击控制台应用程序 (testES),然后单击“设为启动项目”。
- 将以下源代码粘贴到 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"); }
- 按 F5 以运行测试代码。
在代码的步骤 7 中,对 dbAccess 的第一个调用成功完成。产品 1 和产品 2 均在 Products 表中。产品 1 的 onOrder 字段更新为 777,产品 2 的 inStock 字段更新为 888。因为这个事务成功完成,所以您将在输出窗口中收到以下消息
对 dbAccess 的第二个调用失败。因此,不会将 dbAccess 中的对 Products 表的任何更新语句发布到数据库。虽然产品 1 本来可以将它的 onOrder 字段更新为 5,但是产品 2 不能将它的 inStock 字段设为 -20。(因为 Products 表定义中定义的一个约束条件规定 inStock 不允许为负数)。
因此,对 dbAccess 的这个调用失败了,从而整个事务也随之失败。Products 表将保持调用 dbAccess 之前的状态。catch语句处理来自 dbAccess 的事务失败通知,您将在输出窗口中收到以下错误消息:TRANSACTION FAILURE - 使用 SQL Server 企业管理器检查罗斯文的 Products 表的内容。查看产品 1,onOrder 字段的值为 777。查看产品 2,instock 字段的值为 888。因此,对 dbAccess 的第二个调用(此调用本会使这些字段具有不同的值)失败了。
疑难解答
- 确保使用 COM+ 服务的所有项目都有一个强名称。
- 使用 COM+ 服务的所有类都必须继承服务组件。服务组件位于 System.EnterpriseServices 命名空间中。
- 进行调试时,事务在提交或终止前可能会超时。要避免出现超时,请在事务属性中使用一个超时属性。在下面的示例中,在完成任何事务时,关联的方法在超时前都有 1,200 秒的执行时间:
[Transaction(TransactionOption.Required,timeout:=1200)]