使用DataSet和DataAdapter插入关系数据






4.68/5 (130投票s)
2003年3月16日
6分钟阅读

1204727

9893
本文介绍将数据插入 DataSet,然后将这些更改提交到数据库的过程。它针对数据库中存在 IDENTITY/AutoNumber 列时出现的问题。
引言
自 ADO.NET 推出以来,我们在数据库驱动的应用程序开发中开始使用不同的概念——断开连接的环境。大多数人已经意识到了这个变化,但这种新的开发模型带来了一些需要在完全断开连接的环境中才能解决的问题,以使我们的应用程序正常工作。
我使用这种方法开发应用程序时发现的一个问题是使用 DataSet
和 DataAdapter
对象更新关系数据的方法。如果您只更新一个 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
)。我们使用了 DataAdapter
的 FillSchema
方法来创建 DataSet
中的两个 DataTable
对象,以便它们具有与数据库表相同的结构(包括 AutoNumber
字段)。
我们还使用了 OleDbCommand
生成器为两个 DataAdapter
创建了额外的命令,以便稍后我们可以将 DataSet
中的更改提交到数据库。
创建 DataTable 之间的关系
现在我们需要在两个表的两个键列之间添加关系。我们将通过创建一个新的 DataRelation
对象并将其附加到我们 DataSet
的 Relations
集合来实现这一点。以下代码正是这样做的。
// 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
对象,将其添加到 DataSet
的 Relations
集合中。
插入数据
现在一切都已准备就绪,我们需要在更新 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
中的值更新后,我们需要调用 Row
的 AcceptChanges
方法,以便将此行保持在“已更新”状态,而不是“已更改”状态。
如果您再次尝试执行代码,您将看到 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
参数。此参数的“方向”与其他参数不同。该参数具有输出方向,并且源列映射到 DataTable
的 OrderId
列。通过这种结构,每次执行后,存储过程都会将值返回到此参数,该值将被复制到 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 日:初始版本
许可证
本文没有明确的许可证附加,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下面的讨论区联系作者。作者可能使用的许可证列表可以在 这里 找到。