C#-使用DataAdapter 和DataSet 更新数据库

DataAdapter的Update方法可调用来将DataSet中的更改解析回数据源。与Fill方法类似,Update方法将DataSet的实例和可选的DataTable对象或DataTable名称用作参数。DataSet实例是包含已作出的更改的DataSet,而DataTable标识从其中检索更改的表。

当调用Update方法时,DataAdapter将分析已作出的更改并执行相应的命令(INSERT、UPDATE或DELETE)。当DataAdapter遇到对DataRow的更改时,它将使用InsertCommand、UpdateCommand或DeleteCommand来处理该更改。这样,您就可以通过在设计时指定命令语法并在可能时通过使用存储过程来尽量提高ADO.NET应用程序的性能。在调用Update之前,必须显式设置这些命令。如果调用了Update但不存在用于特定更新的相应命令(例如,不存在用于已删除行的DeleteCommand),则将引发异常。

Command参数可用于为DataSet中每个已修改行的SQL语句或存储过程指定输入和输出值。有关更多信息,请参阅将参数用于DataAdapter。

如果DataTable映射到单个数据库表或从单个数据库表生成,则可以利用CommandBuilder对象自动生成DataAdapter的DeleteCommand、InsertCommand和UpdateCommand。有关更多信息,请参阅自动生成的命令。

Update方法会将更改解析回数据源,但是自上次填充DataSet以来,其他客户端可能已修改了数据源中的数据。若要使用当前数据刷新DataSet,请再次使用DataAdapter填充(Fill)DataSet。新行将添加到该表中,更新的信息将并入现有行。

若要处理可能在Update操作过程中发生的异常,可以使用RowUpdated事件在这些异常发生时响应行更新错误(请参阅使用DataAdapter事件),或者可以在调用Update之前将DataAdapter.ContinueUpdateOnError设置为true,然后在Update完成时响应存储在特定行的RowError属性中的错误信息(请参阅添加和读取行错误信息)。

注意如果对DataSet、DataTable或DataRow调用AcceptChanges,则将使某DataRow的所有Original值被该DataRow的Current值改写。如果已修改将该行标识为唯一行的字段值,那么当调用AcceptChanges后,Original值将不再匹配数据源中的值。

以下示例演示如何通过显式设置DataAdapter的UpdateCommand来执行对已修改行的更新。请注意,在UPDATE语句的WHERE子句中指定的参数设置为使用SourceColumn的Original值。这一点很重要,因为Current值可能已被修改,并且可能不匹配数据源中的值。Original值是曾用来从数据源填充DataTable的值。

SqlClient

[VisualBasic]

DimcatDAAsSqlDataAdapter=NewSqlDataAdapter("SELECTCategoryID,CategoryNameFROMCategories",nwindConn)

catDA.UpdateCommand=NewSqlCommand("UPDATECategoriesSETCategoryName=@CategoryName"&_

软件开发网

"WHERECategory,nwindConn)

catDA.UpdateCommand.Parameters.Add("@CategoryName",SqlDbType.NVarChar,15,"CategoryName")

DimworkParmAsSqlParameter=catDA.UpdateCommand.Parameters.Add("@CategoryID",SqlDbType.Int)

workParm.SourceColumn="CategoryID"

workParm.SourceVersion=DataRowVersion.Original

DimcatDSAsDataSet=NewDataSet

catDA.Fill(catDS,"Categories")

DimcRowAsDataRow=catDS.Tables("Categories").Rows(0)

cRow("CategoryName")="NewCategory"

catDA.Update(catDS)

[C#]

SqlDataAdaptercatDA=newSqlDataAdapter("SELECTCategoryID,CategoryNameFROMCategories",nwindConn);

catDA.UpdateCommand=newSqlCommand(UPDATECategoriesSETCategoryName=@CategoryName,"WHERECategory,nwindConn);

catDA.UpdateCommand.Parameters.Add("@CategoryName",SqlDbType.NVarChar,15,"CategoryName");

SqlParameterworkParm=catDA.UpdateCommand.Parameters.Add("@CategoryID",SqlDbType.Int);

workParm.SourceColumn="CategoryID";

workParm.SourceVersion=DataRowVersion.Original;

DataSetcatDS=newDataSet();

catDA.Fill(catDS,"Categories");

DataRowcRow=catDS.Tables["Categories"].Rows[0];

cRow["CategoryName"]="NewCategory";

catDA.Update(catDS);

OleDb

[VisualBasic]

DimcatDAAsOleDbDataAdapter=NewOleDbDataAdapter("SELECTCategoryID,CategoryNameFROMCategories",nwindConn)

catDA.UpdateCommand=NewOleDbCommand("UPDATECategoriesSETCategoryName=?"&_

"WHERECategory,nwindConn)

catDA.UpdateCommand.Parameters.Add("@CategoryName",OleDbType.VarChar,15,"CategoryName")

软件开发网

DimworkParmAsOleDbParameter=catDA.UpdateCommand.Parameters.Add("@CategoryID",OleDbType.Integer)

workParm.SourceColumn="CategoryID"

workParm.SourceVersion=DataRowVersion.Original

DimcatDSAsDataSet=NewDataSet

catDA.Fill(catDS,"Categories")

DimcRowAsDataRow=catDS.Tables("Categories").Rows(0)

cRow("CategoryName")="NewCategory"

catDA.Update(catDS)

[C#]

OleDbDataAdaptercatDA=newOleDbDataAdapter("SELECTCategoryID,CategoryNameFROMCategories",nwindConn);

catDA.UpdateCommand=newOleDbCommand("UPDATECategoriesSETCategoryName=?"

"WHERECategory,nwindConn);

catDA.UpdateCommand.Parameters.Add("@CategoryName",OleDbType.VarChar,15,"CategoryName");

OleDbParameterworkParm=catDA.UpdateCommand.Parameters.Add("@CategoryID",OleDbType.Integer);

workParm.SourceColumn="CategoryID";

workParm.SourceVersion=DataRowVersion.Original;

DataSetcatDS=newDataSet();

catDA.Fill(catDS,"Categories");

DataRowcRow=catDS.Tables["Categories"].Rows[0];

cRow["CategoryName"]="NewCategory";

catDA.Update(catDS);

自动递增列

如果来自数据源的表包含自动递增列,则可以使用由数据源生成的值填充DataSet中的列,方法是通过以存储过程输出参数的形式返回自动递增值并将其映射到表中的一列,或者使用DataAdapter的RowUpdated事件。有关示例,请参阅检索“标识”或“自动编号”值。

但是,DataSet中的值可能会与数据源中的值不同步并导致意外的行为。例如,请考虑一个包含自动递增主键列CustomerID的表。如果在该DataSet中添加两个新客户,它们将收到自动递增的CustomerId值1和2。在向DataAdapter的Update方法传递第二个客户行时,新添加的行会收到数据源中的自动递增CustomerID值1,该值与DataSet中的值2不匹配。当DataAdapter使用返回值填充DataSet中的行时,由于第一个客户行的CustomerID已经是1,因此将发生约束冲突。

为了避免这种行为,建议在使用数据源中的自动递增列和DataSet中的自动递增列时,在DataSet中创建AutoIncrementStep为-1且AutoIncrementSeed为0的列,并确保数据源生成从1开始并以正步长值递增的自动递增标识值。这样,DataSet将为自动递增值生成负数,这些负数不会与数据源所生成的正自动递增值发生冲突。另一种方法是使用Guid类型的列而不是自动递增列。生成Guid值的算法在DataSet中生成的Guid从不会与数据源生成的Guid相同。有关定义DataTable中的列的更多信息,请参阅定义数据表的架构。

插入、更新和删除的排序

在许多情况下,以何种顺序向数据源发送通过DataSet作出的更改是相当重要的。例如,如果已更新现有行的主键值并且添加了具有新主键值的新行,则务必要在处理插入之前处理更新。

可以使用DataTable的Select方法来返回仅引用具有特定RowState的DataRow数组。然后可以将返回的DataRow数组传递到DataAdapter的Update方法来处理已修改的行。通过指定要更新的行的子集,可以控制处理插入、更新和删除的顺序。

例如,以下代码确保首先处理表中已删除的行,然后处理已更新的行,然后处理已插入的行。

[VisualBasic]

DimupdTableAsDataTable=custDS.Tables("Customers")

'Firstprocessdeletes.

custDA.Update(updTable.Select(Nothing,Nothing,DataViewRowState.Deleted))

'Nextprocessupdates.

custDA.Update(updTable.Select(Nothing,Nothing,DataViewRowState.ModifiedCurrent))

'Finally,processinserts.

custDA.Update(updTable.Select(Nothing,Nothing,DataViewRowState.Added))

[C#]

DataTableupdTable=custDS.Tables["Customers"];

//Firstprocessdeletes.

custDA.Update(updTable.Select(null,null,DataViewRowState.Deleted));

//Nextprocessupdates.

custDA.Update(updTable.Select(null,null,DataViewRowState.ModifiedCurrent));

//Finally,processinserts.

custDA.Update(updTable.Select(null,null,DataViewRowState.Added));