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

使用DataSet和DataAdapter插入关系数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (130投票s)

2003年3月16日

6分钟阅读

viewsIcon

1204727

downloadIcon

9893

本文介绍将数据插入 DataSet,然后将这些更改提交到数据库的过程。它针对数据库中存在 IDENTITY/AutoNumber 列时出现的问题。

引言

自 ADO.NET 推出以来,我们在数据库驱动的应用程序开发中开始使用不同的概念——断开连接的环境。大多数人已经意识到了这个变化,但这种新的开发模型带来了一些需要在完全断开连接的环境中才能解决的问题,以使我们的应用程序正常工作。

我使用这种方法开发应用程序时发现的一个问题是使用 DataSetDataAdapter 对象更新关系数据的方法。如果您只更新一个 DataTable,创建 DataAdapter 的代码不成问题,但如果您处理多个表,并且这些表之间存在父子关系,则更新/插入过程可能有点棘手,特别是当父表具有 IDENTITY/AutoNumber 列时。在本文中,我将展示一些解决此问题的方法,特别是当您在数据库中使用 AutoNumber/IDENTITY 列时。

在示例中,我将使用一个包含 2 个表的数据库,一个表用于存放 Order(订单),另一个表用于存放 OrderDetails(订单明细)。OrderDetails 表在 OrderId 列与 Order 表之间存在外键关系,而 OrderId 列是 IDENTITY/AutoNumber 列。

本文分为两部分,一部分展示使用 Access 2000 数据库的实现,另一部分使用 SQL Server 数据库和存储过程。希望您喜欢!

创建结构 (Access 2000)

为了完成我们的示例,您需要做的第一件事是创建一些基本变量来保存我们的 DataSet 和两个 DataAdapter 对象(一个用于父表,一个用于子表)。我们将使用 System.Data.OleDb 命名空间,因为我们正在处理 Access 2000。

// Create the DataSet object
DataSet oDS = new DataSet();
OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; 
                        Data Source=orders.mdb");
conn.Open();

// Create the DataTable "Orders" in the Dataset and the OrdersDataAdapter
OleDbDataAdapter oOrdersDataAdapter = new 
    OleDbDataAdapter(new OleDbCommand("SELECT * FROM Orders", conn));
OleDbCommandBuilder oOrdersCmdBuilder = new
    OleDbCommandBuilder(oOrdersDataAdapter);
oOrdersDataAdapter.FillSchema(oDS, SchemaType.Source);

DataTable pTable = oDS.Tables["Table"];
pTable.TableName = "Orders";

// Create the DataTable "OrderDetails" in the Dataset 
// and the OrderDetailsDataAdapter
OleDbDataAdapter oOrderDetailsDataAdapter = new
    OleDbDataAdapter(new OleDbCommand("SELECT * FROM OrderDetails", conn));
OleDbCommandBuilder oOrderDetailsCmdBuilder = new
    OleDbCommandBuilder(oOrderDetailsDataAdapter);
oOrderDetailsDataAdapter.FillSchema(oDS, SchemaType.Source);

pTable = oDS.Tables["Table"];
pTable.TableName = "OrderDetails";

在前面的代码示例中,我们创建了到数据库的连接和两个 DataAdapter 对象,一个用于更新父表 (Orders),一个用于更新子表 (OrderDetails)。我们使用了 DataAdapterFillSchema 方法来创建 DataSet 中的两个 DataTable 对象,以便它们具有与数据库表相同的结构(包括 AutoNumber 字段)。

我们还使用了 OleDbCommand 生成器为两个 DataAdapter 创建了额外的命令,以便稍后我们可以将 DataSet 中的更改提交到数据库。

创建 DataTable 之间的关系

现在我们需要在两个表的两个键列之间添加关系。我们将通过创建一个新的 DataRelation 对象并将其附加到我们 DataSetRelations 集合来实现这一点。以下代码正是这样做的。

// Create the relationship between the two tables
oDS.Relations.Add(new DataRelation("ParentChild",
    oDS.Tables["Orders"].Columns["OrderId"],
    oDS.Tables["OrderDetails"].Columns["OrderId"]));

在上面的代码中,我们使用 DataSet (oDS) 获取对 DataTable 对象两个键列的引用,并创建了一个 DataRelation 对象,将其添加到 DataSetRelations 集合中。

插入数据

现在一切都已准备就绪,我们需要在更新 Access 数据库中的数据之前,将数据插入到 DataSet 中。我们将向 Order 表插入一个新行,向 OrderDetails 表插入一个新行。

// Insert the Data
DataRow oOrderRow = oDS.Tables["Orders"].NewRow();
oOrderRow["CustomerName"] = "Customer ABC";
oOrderRow["ShippingAddress"] = "ABC street, 12345";
oDS.Tables["Orders"].Rows.Add(oOrderRow);

DataRow oDetailsRow = oDS.Tables["OrderDetails"].NewRow();
oDetailsRow["ProductId"] = 1;
oDetailsRow["ProductName"] = "Product 1";
oDetailsRow["UnitPrice"] = 1;
oDetailsRow["Quantity"] = 2;

oDetailsRow.SetParentRow(oOrderRow);
oDS.Tables["OrderDetails"].Rows.Add(oDetailsRow);

请注意,我们使用了 SetParentRow 方法来创建两个行之间的关系。这是更新具有关系和 AutoNumber 列的两个表时最重要的一点。

更新 DataSet

现在数据已插入到 DataSet 中,是时候更新数据库了。

oOrdersDataAdapter.Update(oDS, "Orders");
oOrderDetailsDataAdapter.Update(oDS, "OrderDetails");

conn.Close();

解决 AutoNumber 列问题

如果您检查数据库中的数据,您会注意到插入到 OrderDetails 表中的行的 OrderId 列设置为零,而插入到 Orders 表中的 OrderId 设置为一。这是由于 Access 2000 和 DataAdapter 对象之间的一些问题。当 DataAdapter 对象更新第一个表 (Order) 中的数据时,它不会返回生成的 AutoNumber 列值,因此 DataSet 中的 DataTable Orders 内部的值仍然是零。为了解决这个问题,我们需要将 RowUpdate 事件映射到更新此信息。

要映射 RowUpdate 事件,我们将创建一个事件处理程序,并获取新自动生成数字的值以保存在 DataSet 中。您需要在创建 oOrdersDataAdapter 对象后添加这一行代码。

oOrdersDataAdapter.RowUpdated += new
     OleDbRowUpdatedEventHandler(OrdersDataAdapter_OnRowUpdate);

现在我们需要创建一个 EventHandler 函数来处理这个 RowUpdate 事件。

static void OrdersDataAdapter_OnRowUpdate(object sender,
                    OleDbRowUpdatedEventArgs  e)
{
    OleDbCommand oCmd = new OleDbCommand("SELECT @@IDENTITY"
                                         e.Command.Connection);

    e.Row["OrderId"] = oCmd.ExecuteScalar();
    e.Row.AcceptChanges();
}

对于 OrdersDataAdapter 中的每一次更新,我们将调用一个新的 SQL 命令,该命令将获取新插入的 AutoNumber 列值。我们将使用 SELECT @@IDENTITY 命令来做到这一点。此命令仅在 Access 2000/2002 中有效,在早期版本中无效。在 DataSet 中的值更新后,我们需要调用 RowAcceptChanges 方法,以便将此行保持在“已更新”状态,而不是“已更改”状态。

如果您再次尝试执行代码,您将看到 OrderDetails 表中的行现在包含正确的列值。

现在我们将了解如何在 SQL Server 2000 中处理相同的问题。我为 Access 数据库提出的方法同样适用于 SQL Server,但如果您使用的是存储过程,则还有其他方法可以实现。

创建结构 (SQL Server 2000)

我们将从创建与 Access 2000 相同的结构开始,但我们将通过代码创建 DataAdapter 命令,而不是使用 CommandBuilder,因为我们将使用 SQL Server 存储过程来更新数据。

// Create the DataSet object
DataSet oDS = new DataSet();
SqlConnection conn = new SqlConnection("Data Source=.;
        Initial Catalog=Orders;Integrated Security=SSPI");
conn.Open();

// Create the DataTable "Orders" in the Dataset and the OrdersDataAdapter
SqlDataAdapter oOrdersDataAdapter = new
    SqlDataAdapter(new SqlCommand("SELECT * FROM Orders", conn));

oOrdersDataAdapter.InsertCommand = new 
    SqlCommand("proc_InsertOrder", conn);
SqlCommand cmdInsert = oOrdersDataAdapter.InsertCommand;
cmdInsert.CommandType = CommandType.StoredProcedure;

cmdInsert.Parameters.Add(new SqlParameter("@OrderId", SqlDbType.Int));
cmdInsert.Parameters["@OrderId"].Direction = ParameterDirection.Output;
cmdInsert.Parameters["@OrderId"].SourceColumn = "OrderId";

cmdInsert.Parameters.Add(new SqlParameter("@CustomerName", 
                             SqlDbType.VarChar,50,"CustomerName"));
                             
cmdInsert.Parameters.Add(new SqlParameter("@ShippingAddress",
                             SqlDbType.VarChar,50,"ShippingAddress"));

oOrdersDataAdapter.FillSchema(oDS, SchemaType.Source);

DataTable pTable = oDS.Tables["Table"];
pTable.TableName = "Orders";

// Create the DataTable "OrderDetails" in the
// Dataset and the OrderDetailsDataAdapter

SqlDataAdapter oOrderDetailsDataAdapter = new
      SqlDataAdapter(new SqlCommand("SELECT * FROM OrderDetails", conn));

oOrderDetailsDataAdapter.InsertCommand = new 
      SqlCommand("proc_InsertOrderDetails", conn);
      
cmdInsert = oOrderDetailsDataAdapter.InsertCommand;
cmdInsert.CommandType = CommandType.StoredProcedure;
cmdInsert.Parameters.Add(new SqlParameter("@OrderId", SqlDbType.Int));
cmdInsert.Parameters["@OrderId"].SourceColumn = "OrderId";
cmdInsert.Parameters.Add(new SqlParameter("@ProductId", SqlDbType.Int));
cmdInsert.Parameters["@ProductId"].SourceColumn = "ProductId";
cmdInsert.Parameters.Add(new 
   SqlParameter("@ProductName", SqlDbType.VarChar,50,"ProductName"));
cmdInsert.Parameters.Add(new 
   SqlParameter("@UnitPrice", SqlDbType.Decimal));
cmdInsert.Parameters["@UnitPrice"].SourceColumn = "UnitPrice";
cmdInsert.Parameters.Add(new SqlParameter("@Quantity", SqlDbType.Int ));
cmdInsert.Parameters["@Quantity"].SourceColumn = "Quantity";

oOrderDetailsDataAdapter.FillSchema(oDS, SchemaType.Source);

pTable = oDS.Tables["Table"];
pTable.TableName = "OrderDetails";

// Create the relationship between the two tables
oDS.Relations.Add(new DataRelation("ParentChild",
    oDS.Tables["Orders"].Columns["OrderId"],
    oDS.Tables["OrderDetails"].Columns["OrderId"]));

在这段代码中,我们手动创建了一个 SqlCommand,通过 DataAdapter 在数据库表中执行所有 insert 操作。每个 SqlCommand 都调用数据库中的一个存储过程,该存储过程的参数结构与表结构相等。

这里最重要的是第一个 DataAdapter 命令的 OrderId 参数。此参数的“方向”与其他参数不同。该参数具有输出方向,并且源列映射到 DataTableOrderId 列。通过这种结构,每次执行后,存储过程都会将值返回到此参数,该值将被复制到 OrderId 源列。OrderId 参数在过程中接收 @@IDENTITY,如下面的示例所示。

CREATE PROCEDURE proc_InsertOrder
(@OrderId int output,
 @CustomerName varchar(50),
 @ShippingAddress varchar(50)
)
 AS

INSERT INTO Orders (CustomerName, ShippingAddress)
VALUES
(@CustomerName, @ShippingAddress)

SELECT @OrderId=@@IDENTITY

插入数据

现在我们已经设置了整个结构,是时候插入数据了。过程与我们使用 Access 数据库时完全相同,使用 SetParentRow 方法来维护关系并确保 IDENTITY 列将被复制到子表 (OrderDetails)。

// Insert the Data
DataRow oOrderRow = oDS.Tables["Orders"].NewRow();
oOrderRow["CustomerName"] = "Customer ABC";
oOrderRow["ShippingAddress"] = "ABC street, 12345";
oDS.Tables["Orders"].Rows.Add(oOrderRow);

DataRow oDetailsRow = oDS.Tables["OrderDetails"].NewRow();
oDetailsRow["ProductId"] = 1;
oDetailsRow["ProductName"] = "Product 1";
oDetailsRow["UnitPrice"] = 1;
oDetailsRow["Quantity"] = 2;

oDetailsRow.SetParentRow(oOrderRow);
oDS.Tables["OrderDetails"].Rows.Add(oDetailsRow);

oOrdersDataAdapter.Update(oDS, "Orders");
oOrderDetailsDataAdapter.Update(oDS, "OrderDetails");

conn.Close();

如果您检查数据库,您将看到 OrderId 列已使用正确的 IDENTITY 列值进行了更新。

历史

  • 2003 年 3 月 16 日:初始版本

许可证

本文没有明确的许可证附加,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下面的讨论区联系作者。作者可能使用的许可证列表可以在 这里 找到。

© . All rights reserved.