65.9K
CodeProject 正在变化。 阅读更多。
Home

使用多个TableAdpter进行数据库事务处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.16/5 (11投票s)

2008 年 3 月 13 日

CPOL

7分钟阅读

viewsIcon

50567

downloadIcon

809

一篇关于在利用部分类的同时,使用强类型数据集和多个 TableAdapter 来处理事务的文章。

引言

TableAdapter 与强类型数据集结合使用,提供了填充 DataTable 或向数据库提交更新的功能。虽然单个查询在 TableAdapter 中工作良好,但将事务包装在多个 TableAdapter 的多个查询周围可能会带来挑战。在本文中,我将引导您完成在使用多个 TableAdapter 时执行事务的过程。

本文面向具有 ADO.NET、强类型数据集、DataTable、TableAdapter 和部分类工作知识的程序员。如果您不熟悉其中任何一个主题,那么本文可能不容易理解。

摘要

使用多个 TableAdapter 处理事务,基本上只需要三件事

  1. 每个 TableAdapter 必须共享相同的 Connection 对象。
  2. 必须从共享的 Connection 创建一个 Transaction 对象。
  3. 将要在事务中执行的每个 Command 对象都必须将其 Transaction 属性分配给在步骤 2 中创建的 Transaction 对象。

虽然可能有几种不同的方法来满足这些条件,但我将采用一种依赖于部分类的方法。

场景

最近,我被委派编写一个应用程序,用于从第三方供应商开发的系统中外部管理文档。每个文档相关的数据存储在 Oracle 数据库的多个表中。添加一个新文档到系统中,需要在三个独立表中各插入一条记录,并在另一个表中插入十二条记录。

为添加到系统中的每个新文档,必须执行十五个插入命令。如果其中任何一个插入失败,那么先前命令所做的任何更改都必须撤销。获得此功能的最佳方法是将命令分组到一个事务中。但是,我想从强类型数据集内部执行此操作,这带来了问题。

范围

出于本文的目的,我将使用 SQL Express 而不是 Oracle。我将保持代码简单,并且不包含诸如验证、异常处理等内容。我还会将示例数据库限制为两个表,使用没有实际意义的数据。本文唯一的目的是提供对如何在事务中使用多个 TableAdapter 的良好理解。

Data

在数据库中,有两个表。EMPLOYEES 表(父表)包含两列:IDNAMEEMP_PROPERTIES 表包含四列:IDNAMEVALUEEMP_ID。每个员工可以有一个或多个相关的属性。每个属性必须且只能属于一个员工。

解决方案设置

要开始,请创建一个新的空白解决方案并将其命名为Demo1

接下来,添加一个名为Demo1_DAL的新类库项目。此项目将用于我们的数据访问层。删除自动添加的默认Class1.cs文件。

添加第二个项目,使用控制台应用程序模板,并接受默认名称ConsoleApplication1。此项目将用作简单的测试界面。本文的源代码包含一个名为Demo1.mdf的 SQL Express 文件。将此文件添加到您的ConsoleApplication1项目中。

数据访问层

Demo1_DAL项目添加一个名为DataSet1.xsd的新DataSet。现在应该可以看到 Visual Studio 的数据集设计器。如果Demo1.mdf数据库未在服务器资源管理器中显示,请在解决方案资源管理器中双击该文件将其打开。在服务器资源管理器中,展开Data Connections下的Demo1.mdfTables文件夹。将EMPLOYEES表拖到数据集设计器上,然后对EMP_PROPERTIES表执行相同的操作。

默认情况下,TableAdapter 会自动创建 InsertUpdateDelete 方法。尽管 Insert 方法包含用于检索 ID 列的 SCOPE_IDENTITY() 的 SQL 代码,但该查询使用的是 DataAdapter 的 ExecuteNonQuery() 方法,而不是 ExecuteScalar()。我没有找到可以更改此设置的地方。(谢谢你,微软,这很有道理。)有多种解决方法,但我选择为 TableAdapter 添加一个新的 Insert 方法。

在数据集设计器中,右键单击 EMPLOYEESTableAdapter,然后从菜单中选择“Add Query...”。继续通过向导,添加一个新的 Insert 查询,SQL 如下:

INSERT INTO [dbo].[EMPLOYEES] ([NAME]) VALUES (@NAME);
SELECT ID FROM EMPLOYEES WHERE (ID = SCOPE_IDENTITY())

由于 EMPLOYEESTableAdapter 已经存在一个 Insert 方法,您必须为新的 Insert 查询指定一个不同的名称。我将我的命名为 InsertScalar()。添加查询后,右键单击数据集设计器中的方法签名,然后选择“Properties”菜单项。将 ExecuteMode 设置为 Scalar。保存更改并关闭数据集设计器。

接下来,我们将使用部分类来扩展我们的 DataSet 和 TableAdapter 的功能。这将为事务提供所需的 D支持。在解决方案资源管理器中右键单击 DataSet1.xsd,然后选择“View Code”菜单选项。这将创建一个 DataSet1.cs 文件,其中包含以下代码:

namespace Demo1_DAL
{
    partial class DataSet1
    {
    }
}

在这里,我们将添加我们自己的自定义方法到 DataSet1 类。默认情况下,只为我们创建了 DataSet1 类的声明。我们还将扩展数据集中使用的每个 TableAdapter 的功能。由于这些类存在于不同的命名空间中,我们必须在 DataSet1 代码文件中添加第二个命名空间。将以下代码添加到 DataSet1 代码文件的末尾:

namespace Demo1_DAL.DataSet1TableAdapters
{
    partial class EMPLOYEESTableAdapter
    {
    }
    partial class EMP_PROPERTIESTableAdapter
    {
    }
}

注意:用于扩展设计器生成类的功能的方法通常被称为“辅助”方法。

我们需要提供一个公共机制,将我们的 Transaction 对象分配给我们的每个 Command 对象。这些 Command 对象属于 TableAdapter 中使用的 DataAdapter。由于 DataAdapter 是 TableAdapter 对象的私有属性,我们无法直接从 TableAdapter 类外部访问 Command 对象。为了适应这一点,我们将为 TableAdapter 添加自己的公共方法。

请记住,我们为 EMPLOYEESTableAdapter 添加了一个名为 InsertScalar() 的自定义插入方法。我们还需要将我们的 Transaction 分配给它的 Command 对象。由于此 Command 存储在 Command 对象数组中,我们将把数组中的所有 Command 对象都分配给 Transaction。(这可以避免我们硬编码数组中 Command 的确切位置。如果将来向 TableAdapter 添加更多查询,那么这个位置可能会改变。)

为此,请将以下方法添加到 EMPLOYEESTableAdapterEMP_PROPERTIESTableAdapter 类中:

public void AttachTransaction(System.Data.SqlClient.SqlTransaction t)
{
    this.Adapter.InsertCommand.Transaction = t;
    this.Adapter.UpdateCommand.Transaction = t;
    this.Adapter.DeleteCommand.Transaction = t;
    foreach (System.Data.SqlClient.SqlCommand cmd
             in this.CommandCollection)
    {
        cmd.Transaction = t;
    }
}

这是我们需要对 TableAdapter 类进行的唯一一项添加。

接下来,我们将为我们的 DataSet1 类添加一个名为 InsertEmployee 的新方法。此方法将被声明为静态的,因此无需实例化 DataSet1 对象即可调用它。此方法需要两个参数:一个包含员工姓名的 string,以及一个包含每个员工属性的键值对集合的 Hashtable。此方法还将返回受影响的行数。方法声明应如下所示:

public static int InsertEmployee(string pName,
                  System.Collections.Hashtable pProps)
{
}

构成此方法主体的代码列在下面。我在代码中添加了注释以帮助解释其作用:

int myReturnValue = 0;  // Rows affected
// Create Table Adapters
DataSet1TableAdapters.EMPLOYEESTableAdapter taEmp = new
                  DataSet1TableAdapters.EMPLOYEESTableAdapter();
DataSet1TableAdapters.EMP_PROPERTIESTableAdapter taProps = new
                  DataSet1TableAdapters.EMP_PROPERTIESTableAdapter();
// Open a Connection on one of the adapters and
// Make sure All Adapters are using the same connection.
taEmp.Connection.Open();
taProps.Connection = taEmp.Connection;
// Start a Transaction on the Open Connection
System.Data.SqlClient.SqlTransaction myTrans =
                  taEmp.Connection.BeginTransaction();
// Assign the Transaction to our Table Adpaters Command objects
taEmp.AttachTransaction(myTrans);
taProps.AttachTransaction(myTrans);
// Process Updates
try
{
    //Retrieve SCOPE_IDENTITY() of new Employee
    int myEmpID = System.Convert.ToInt32(taEmp.InsertScalar(pName));
    myReturnValue += 1;
    foreach (System.Collections.DictionaryEntry de in pProps)
    {
        taProps.Insert(de.Key.ToString(),
        de.Value.ToString(), myEmpID);
        myReturnValue += 1;
    }
    // Commit Updates
    myTrans.Commit();
}
catch
{
    // Rollback on any Exception
    myTrans.Rollback();
    myReturnValue = 0;
}
finally
{
    // Dispose of unmanaged resources
    myTrans.Dispose();
    taProps.Dispose();
    taEmp.Dispose();
}
return myReturnValue;

InsertEmployee 方法中,我故意保持参数的简单性。在某些情况下,传递其他对象(如数据集、数据表、数据行等)可能会更好。

请注意,我还将 InsertEmployee 方法包含在 DataSet1 类中。如果您想能够将此方法绑定到对象数据源,那么您也可以将其包含在 TableAdapter 中。

至此,数据访问层所需代码已完成。保存更改并生成 Demo1_DAL 项目。

测试

现在我们的 DAL 已完成,我们可以对其进行测试了。在这一部分,我们将处理 ConsoleApplication1 项目。

我们需要做的第一件事是添加对 DAL 的引用。右键单击 ConsoleApplication1 项目,然后从菜单中选择“Add Reference...”。选择“Projects”选项卡,确保 Demo1_DAL 项目已高亮显示,然后单击 OK。

接下来,我们将创建一些简单的控制台提示来收集数据并将其插入数据库。将以下代码添加到 Program.cs 文件的 Main 方法中:

string LoopAgain;
string EmpName;
string PropName;
string PropVal;
do
{
    System.Collections.Hashtable EmpProps =
                          new System.Collections.Hashtable();
    Console.WriteLine("Enter Employees Name:");
    EmpName = Console.ReadLine();
    do
    {
        Console.WriteLine("Enter Property Name:");
        PropName = Console.ReadLine();
        Console.WriteLine(string.Format("Enter value for {0}:",
                                        PropName));
        PropVal = Console.ReadLine();
        EmpProps.Add(PropName, PropVal);
        Console.WriteLine("Add another Property?");
        LoopAgain = Console.ReadLine();
    }
    while (LoopAgain.ToUpper() == "Y");
    int RowsAdded = Demo1_DAL.DataSet1.InsertEmployee(EmpName,
                                                      EmpProps);
    Console.WriteLine(string.Format("{0} records added.",
                                    RowsAdded.ToString()));
    Console.WriteLine("Add another Employee?");
    LoopAgain = Console.ReadLine();
}
while (LoopAgain.ToUpper() == "Y");

确保将 ConsoleApplication1 项目设置为启动项目,然后运行它。

结论

部分类是 Visual Studio 工具箱中一个非常强大的工具。一旦您了解了类的基本结构,扩展其功能就变得非常容易。数据集和 TableAdapter 在设计时并未提供默认支持事务的功能。通过使用部分类,我们可以非常轻松地添加此支持和/或其他我们想要的任何功能。

© . All rights reserved.