使用DataSet和DataAdapter插入关联数据
自从介绍了ADO.NET,我们就开始在数据驱动的应用程序开发里使用一个不同的概念,脱机环境。大多数人已经知道了这个改变,但是这个新的开发模型带来一些我们需要解决的问题,以使我们的程序运行在完全的脱机环境中。
在我使用这个方法开发程序的时候我发现使用DataSet和DataAdapter对象更新关系数据的方法有问题。如果你仅仅更新一个DataTable,那么为DataAdapter创建代码就没多大问题,但是如果你使用多于一个表,那些表之间有parent-child关系,那么更新/插入就要有点手段了,特别是父表有一个IDENTITY/AutoNumber列。在这篇文章里,我会展示一些技术来处理这个问题,特别是你在你的数据库里有AutoNumber/IDENTITY列要处理的时候。
在示例里我将使用含有两个表的一个数据库,一个保存Order,一个保存OrderDetails。OrderDetails表与Order表有一个外键关系,是用过OrderId 列来关联的,这是一个IDENTITY/AutoNumber列。
这篇文章分为两个部分,一个展示了使用Access 2000 数据库的实现,另一个展示SQL Server数据库使用储存过程的实现。希望你会喜欢!
建立结构 (Access 2000)
为了完成我们的示例你需要做的第一件事就是创建一些基本变量来保存我们的DataSet和两个DataAdapter对象(一个为父表,一个为子表)。因为我们处理的是Access 2000,所以我们将使用System.Data.OleDb命名空间。
// 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 builder来为两个DataAdapter创建附加的命令,这样我们待会就能把在DataSets里的修改提交给数据库。
在DataTables之间建立联系
现在我们需要为两个表的键添加关系。我们创建一个新的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 表里。
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);
更新DataSet
现在我们已经把数据插入了DataSet,是时候更新数据库了。
oOrdersDataAdapter.Update(oDS, "Orders");
oOrderDetailsDataAdapter.Update(oDS, "OrderDetails");
conn.Close();
搞定列条目自动计数
如果你检查数据库里的数据,你将会注意到插入到OrderDetails 表里的记录行把OrderId 列设为了0,插入到Orders 表的OrderId 设为了1。发生这样的情况跟Access2000和DataAdapter的一些问题有关。
为了描绘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的每次更新时,我们调用一个用来获取新插入的AutoNumber 列值的新的SQL命令。我们使用SELECT @@IDENTITY命令来做这个。这个命令只用在Access 2000/2002,而不是在(Access)以前的版本里。在DataSet里面的值更新以后,我们需要调用Row的AcceptChanges方法,以使这个行保持在“Updated”状态,而不是“changed”状态。
如果你再次试着执行代码,你将发现现在OrderDetails表的行在列里保存了正确的值。
现在我们将来看看在SQL Server2000来处理相同的问题。我为Aceess数据库展现的方法也能应用在SQL Sever里,但是如果你想使用储存过程,有其他的方法来做这个
创建数据库结构 (SQL Server 2000)
我们将开始于创建我们为Access2000使用的相同的结构,但是与前面使用CommandBuilder创建DataAdapter命令不同,我们使用代码创建它们,因为我们要用一个SQL储存过程来更新数据。
// 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来实现数据表的所有插入。每个SqlCommand调用数据库里的一个储存过程,这个数据库有等同于表结构的参数结构。
在这最重要的东西就是在第一个DataAdapter命令里的OrderId 参数。这个参数跟其他的相比有一个不同的direction。这个参数有一个output direction和一个映射到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 列值。