对于在运行时动态指定 SelectCommand(例如通过采用用户提供的文本命令的查询工具)的情况,可能无法在设计时指定合适的 InsertCommand、UpdateCommand 或 DeleteCommand。如果 DataTable 映射到单个数据库表或从单个数据库表生成,则可以利用 CommandBuilder 对象自动生成 DataAdapter 的 DeleteCommand、InsertCommand 和 UpdateCommand。
为了自动生成命令,必须设置 SelectCommand 属性,这是最低的要求。SelectCommand 所检索的表架构确定自动生成的 INSERT、UPDATE 和 DELETE 语句的语法。
为了返回构造插入、更新和删除命令所必需的元数据,CommandBuilder 必须执行 SelectCommand。因此,必须额外经历一次到数据源的行程,这可能会降低性能。若要实现最佳性能,请显式指定命令而不是使用 CommandBuilder。
SelectCommand 还必须返回至少一个主键或唯一列。如果不存在任何主键或唯一列,则将生成 InvalidOperation 异常,并且不会生成命令。
当与 DataAdapter 关联时,CommandBuilder 将自动生成 DataAdapter 的 InsertCommand、UpdateCommand 和 DeleteCommand 属性(如果它们是空引用)。如果已存在用于某属性的 Command,则将使用现有 Command。
通过联接两个或更多个表来创建的数据库视图不会被视为单个数据库表。在这种情况下,将无法使用 CommandBuilder 来自动生成命令,而需要显式地指定命令。有关显式设置命令以便将对 DataSet 的更新解析回数据源的信息,请参阅使用 DataAdapter 和 DataSet 更新数据库。
您可能需要将输出参数映射回 DataSet 的更新行。一项常见的任务是从数据源中检索自动生成的标识字段或时间戳的值。默认情况下,CommandBuilder 不会将输出参数映射到更新行中的列。在这种情况下,将需要显式指定命令。有关将自动生成的标识字段映射回插入行的列的示例,请参阅检索“标识”或“自动编号”值。
自动生成命令的规则
下表显示创建自动生成命令的规则。
| 命令 | 规则 |
|---|---|
| InsertCommand | 为表中 RowState 为 DataRowState.Added 的所有行在数据源中插入一行。为所有可更新的列(不包括标识列、表达式列或时间戳列)插入值。 |
| UpdateCommand | 为表中 RowState 为 DataRowState.Modified 的所有行更新数据源中的行。更新除不可更新的列(如标识列或表达式列)之外所有列的值。更新符合以下条件的所有行:数据源中的列值匹配行的主键列值,或者数据源中的剩余列匹配行的初始值。有关更多信息,请参阅本主题中有关“更新和删除的开放式并发模型”的部分。 |
| DeleteCommand | 为表中 RowState 为 DataRowState.Deleted 的所有行删除数据源中的行。删除符合以下条件的所有行:列值匹配行的主键列值,或者数据源中的剩余列匹配行的初始值。有关更多信息,请参阅本主题中有关“更新和删除的开放式并发模型”的部分。 |
更新和删除的开放式并发模型
为 UPDATE 和 DELETE 语句自动生成命令的逻辑是基于开放式并发的。也就是说,记录不会因编辑而被锁定,而随时可以由其他用户或进程来进行修改。由于在从 SELECT 语句中返回某记录之后但在发出 UPDATE 或 DELETE 语句之前,该记录可能已被修改,所以自动生成的 UPDATE 或 DELETE 语句包含一个 WHERE 子句,这样只有在行包含所有初始值并且尚未从数据源中删除时,才会更新该行。这样做的目的是为了避免改写新数据。如果自动生成的更新命令试图更新已删除或不包含 DataSet 中初始值的行,该命令将不会影响任何记录,并且会引发 DBConcurrencyException。
如果要使 UPDATE 或 DELETE 完成而不考虑初始值,将需要为 DataAdapter 显式设置 UpdateCommand,而不要依赖于自动命令生成。
自动命令生成逻辑的限制
自动命令生成存在以下限制。
仅限于无关表
自动命令生成逻辑为独立表生成 INSERT、UPDATE 或 DELETE 语句,而不考虑与数据源中其他表的任何关系。因此,当调用 Update 来为参与数据库中外键约束的列提交更改时,可能会遇到失败。若要避免这一异常,请不要使用 CommandBuilder 来更新参与外键约束的列,而应显式地指定用于执行该操作的语句。
表名称和列名称
如果列名称或表名称包含任何特殊字符(如空格、句点、问号或其他非字母数字字符),即使这些字符用中括号分隔,自动命令生成逻辑仍会失败。支持格式为 schema.owner.table 的完全限定表名称。
使用 CommandBuilder 自动生成 SQL 语句
若要为 DataAdapter 自动生成 SQL 语句,请首先设置 DataAdapter 的 SelectCommand 属性。然后,创建一个 CommandBuilder 对象并以参数形式指定 CommandBuilder 将为其自动生成 SQL 语句的 DataAdapter。
[Visual Basic]
Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT * FROM
Customers", nwindConn)
Dim custCB As SqlCommandBuilder = New SqlCommandBuilder(custDA)
Dim custDS As DataSet = New DataSet
nwindConn.Open()
custDA.Fill(custDS, "Customers")
' Code to modify data in the DataSet here.
' Without the SqlCommandBuilder, this line would fail.
custDA.Update(custDS, "Customers")
nwindConn.Close()
[C#]
SqlDataAdapter custDA = new SqlDataAdapter("SELECT * FROM
Customers", nwindConn);
SqlCommandBuilder custCB = new SqlCommandBuilder(custDA);
DataSet custDS = new DataSet();
nwindConn.Open();
custDA.Fill(custDS, "Customers");
// Code to modify data in the DataSet here.
// Without the SqlCommandBuilder, this line would fail.
custDA.Update(custDS, "Customers");
nwindConn.Close();
修改 SelectCommand
如果在自动生成插入、更新或删除命令后修改 SelectCommand 的 CommandText,则可能会发生异常。如果已修改的 SelectCommand.CommandText 所包含的架构信息与自动生成插入、更新或删除命令时所使用的 SelectCommand.CommandText 不一致,则以后对 DataAdapter.Update 方法的调用可能会试图访问 SelectCommand 引用的当前表中已不存在的列,并且会引发异常。
可以通过调用 CommandBuilder 的 RefreshSchema 方法来刷新 CommandBuilder 用来自动生成命令的架构信息。
如果您希望知道什么命令已自动生成,可以使用 CommandBuilder 对象的 GetInsertCommand、GetUpdateCommand 和 GetDeleteCommand 方法来获取对自动生成命令的引用,然后检查关联 Command 的 CommandText 属性。
以下代码示例向控制台写入已自动生成的更新命令。
Console.WriteLine(custCB.GetUpdateCommand().CommandText)
以下示例继续上一示例(在“使用 CommandBuilder 自动生成 SQL 语句”部分)中的代码,重新创建 Customers 表,并将 CompanyName 列替换为 ContactName 列。然后调用 RefreshSchema 方法,使用此新列的信息来刷新自动生成的命令。
[Visual Basic]
nwindConn.Open()
custDA.SelectCommand.CommandText = "SELECT CustomerID, ContactName FROM
Customers"
custCB.RefreshSchema()
custDS.Tables.Remove(custDS.Tables("Customers"))
custDA.Fill(custDS, "Customers")
' Code to modify the new table in the DataSet here.
' Without the call to RefreshSchema, this line would fail.
custDA.Update(custDS, "Customers")
nwindConn.Close()
[C#]
nwindConn.Open();
custDA.SelectCommand.CommandText = "SELECT CustomerID, ContactName FROM
Customers";
custCB.RefreshSchema();
custDS.Tables.Remove(custDS.Tables["Customers"]);
custDA.Fill(custDS, "Customers");
// Code to modify the new table in the DataSet here.
// Without the call to RefreshSchema, this line would fail.
custDA.Update(custDS, "Customers");
nwindConn.Close();