在 概述插入、更新和删除数据中我们讨论过, GridView 控件提供了内建的更新与删除功能,而 DetailsView 和 FormView 控件除了这些之外还拥有插入功能。这些数据修改功能可以直接接入到数据源控件中而不需要编写任何代码。 概述插入、更新和删除数据讲解了如何使用 ObjectDataSource 来帮助 GridView 、 DetailsView 以及 FormView 控件完成插入、更新以及删除操作。 ObjectDataSource 能工作的地方, SqlDataSource 也行。
回忆一下,要使 ObjectDataSource 支持插入、更新和删除功能,我们需要定义一些用以执行插入、更新和删除动作的对象层方法。而在 SqlDataSource 中,我们则需要提供 INSERT 、 UPDATE 以及 DELETE 语句(或存储过程)。正如我们将要在本节教程中看到的那样,这些语句可以手工创建,也可以通过SqlDataSource 的“配置数据源”向导自动生成。
注意: 由于我们已经讨论过了 GridView 、 DetailsView 以及 FormView 控件的插入、编辑和删除功能,本教程中我们将重点讨论如何配置SqlDataSource 以使其支持这些操作。如果你需要温习一下如何在 GridView 、 DetailsView 以及 FormView 中实现这个功能,请回到“编辑插入和删除数据”的章节,从 概述插入、更新和删除数据开始。
第一步:指定 INSERT 、 UPDATE 以及 DELETE 语句
就像我们在上两节教程中看到的那样,要从 SqlDataSource 控件中获取数据,我们需要设置两个属性:
1. ConnectionString ,它指定了查询应该发送到的那个数据库;
2. SelectCommand ,它指定了用于返回记录的 SQL 语句或存储过程。
对于带参数的 SelectCommand ,其参数值通过 SqlDataSource 的 SelectParameters 集合来进行指定,可以包含硬编码值和通用参数源(比如 QueryString 、 Session 、 Web 控件等等),也可以通过编程的方式来对其进行赋值。当 SqlDataSource 控件的 Select() 方法被调用时(无论是通过编程来调用或是由数据 Web 控件自动调用),一个数据库连接将被建立,然后参数值被赋值给查询,然后command 被发送到数据库。结果将以 DataSet 或 DataReader 的形式返回,具体的返回形式取决于这个控件的 DataSourceMode 属性的值。
除了获取数据之外, SqlDataSource 还可以以非常相似的办法通过提供 INSERT 、 UPDATE 以及 DELETE 语句来插入、更新以及删除数据。只需简单的给InsertCommand 、 UpdateCommand 以及 DeleteCommand 属性赋上 INSERT 、 UPDATE 以及 DELETE 语句就可以了。如果语句带有参数(这也是常有的事),把这些参数放到 InsertParameters 、 UpdateParameters 以及 DeleteParameters 集合里就可以了。
一旦 InsertCommand 、 UpdateCommand 或 DeleteCommand 的值被指定,相应数据 Web 控件的智能标签中的 “ 允许插入 ” 、 “ 允许编辑 ” 或 “ 允许删除 ” 选项将会变为可用。为了说明这个问题,我们就以在教程 使用SqlDataSource 控件查询数据中创建的 Querying.aspx 为例,给它加上删除功能。
首先从 SqlDataSource 文件夹中打开 InsertUpdateDelete.aspx 和 Querying.aspx 。在 Querying.aspx 的设计器中选择第一个例子中的 SqlDataSource 和 GridView (就是 ProductsDataSource 和 GridView1 )。选好了这两个控件之后,在“编辑”菜单中选择“复制”(或者直接 Ctrl+C )。然后,到 InsertUpdateDelete.aspx 的设计器中,并将这两个控件粘贴上去。将这两个控件弄到 InsertUpdateDelete.aspx 上之后,在浏览器中测试一下这个页面。你应该看到 Products 表中所有记录的 ProductID 、 ProductName 以及 UnitPrice 。
图一: 所有的产品都列出来了,并以 ProductID 进行了排序
添加 SqlDataSource 的 DeleteCommand 以及 DeleteParameters 属性
现在,我们拥有了一个简单的从 Products 表返回所有记录的 SqlDataSource 和一个用以显示这些数据的 GridView 。我们的目标是扩展这个例子以允许用户可以通过 GridView 来删除产品。要达到这个目标,我们需要给 SqlDataSource 控件的 DeleteCommand 以及 DeleteParameters 属性指定相关的值,并配置 GridView 以使其可以支持删除。
DeleteCommand 以及 DeleteParameters 属性可以通过很多种方式进行指定:
· · 通过声明标记代码;
· · 通过设计器的属性窗口;
· · 通过“配置数据源”向导中的 “ 指定一个自定义 SQL 语句或存储过程 ” 页;
· · 通过“配置数据源”向导中的“指定一个表或视图中的列”页上面的“高级”按钮,它将自动的生成DELETE 语句,并自动生成 DeleteCommand 以及 DeleteParameters 属性所使用到的那些参数集合。
我们将在第二步中解释如何自动创建一个 DELETE 语句。虽然“配置数据源”向导或声明标记代码也 OK ,不过现在我们还是使用设计器中的属性窗口。
在 InsertUpdateDelete.aspx 的设计器中,单击一下 ProductsDataSource ,然后打开属性窗口(在“视图”菜单中选择“属性窗口”,或直接按 F4 )。选择 DeleteQuery 属性。
图二: 在属性窗口中选择 DeleteQuery 属性
注意: SqlDataSource 并没有 DeleteQuery 属性。事实上, DeleteQuery 是 DeleteCommand 和 DeleteParameters 属性的结合体,它仅仅在通过设计器查看属性窗口时才显示在那里。如果你在源视图中查看属性窗口,你将只能找到DeleteCommand 属性。
点击 DeleteQuery 属性中的那个按钮,这时会弹出“命令和参数编辑器”对话框(见图三)。在这里,你可以指定 DELETE 语句并为其指定参数。在“ DELETE 命令”输入框中填上如下代码(你可以手工填写,也可以使用查询生成器,反正随你高兴):
1 DELETE FROM Products
2 WHERE ProductID = @ProductID
然后,点击“刷新参数”按钮以将 @ProductID 参数添加到下面的参数列表中。
图三: 命令和参数编辑器(译者注:原文错了,“Select the DeleteQuery Property from the PropertiesWindow ”,这是图二的描述)
不要 给这个参数赋值(保留其参数源为“无”)。在我们向 GridView 添加了删除支持后, GridView 将通过其 DataKeys 集合为按下了删除按钮的那一行自动提供这个参数值。
注意: DELETE 语句中使用的参数名 必须 跟 GridView 、 DetailsView 或 FormView 的 DataKeyNames 相同。也就是说,我们专门把 DELETE 中的那个参数命名为 @ProductID (而不是 @ID ),是因为 Products 表中主键列的名称为 ProductID ( 因此 GridView 的 DataKeyNames 值也是这个 ) 。如果参数名与 DataKeyNames 不同, GridView 将无法自动通过其 DataKeys 集合为这个参数赋值。
在“命令和参数编辑器”对话框中输入了与删除相关的信息之后,点击“确定”,并去到源视图中看看现在的声明标记代码:
1 < asp:SqlDataSource ID="ProductsDataSource" runat ="server"
2 ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
3 SelectCommand =
4 "SELECT [ProductID], [ProductName], [UnitPrice] FROM [Products]"
5 DeleteCommand ="DELETE FROM Products WHERE ProductID = @ProductID">
6 < DeleteParameters>
7 < asp:Parameter Name ="ProductID" />
8 DeleteParameters>
9 asp:SqlDataSource >
注意看看新添加的 DeleteCommand 属性和
配置 GridView 以使其可以删除
由于添加了 DeleteCommand 属性, GridView 的智能标签现在有了“允许删除”选项。勾上这个复选框。就像我们在 概述插入、更新和删除数据中所讨论的那样,在 GridView 的 ShowDeleteButton 属性设为 true 的时候,这将使其添加一个 CommandField 。如图四所示,当通过浏览器访问这个页面时, GridView 中将包含一个删除按钮。通过删除一些产品来测试一下这个页面吧。
图四: 现在,每一个 GridView 行都包含了一个删除按钮
点击删除按钮之后,会引发一个回发,然后 GridView 将这一行的 DataKeys 集合赋值给 ProductID 参数,并调用 SqlDataSource 的 Delete() 方法。接着, SqlDataSource 控件就连接上数据库并执行这个 DELETE 语句。最后 GridView 重新绑定这个 SqlDataSource ,并显示现在的产品集(刚刚被删除的那条记录就不见了)。
注意: 由于 GridView 使用其 DataKeys 集合来存放 SqlDataSource 的参数,所以我们必须注意要将 GridView 的 DataKeyNames 属性设置为主键列(也可能是复合主键),而且 SqlDataSource 的 SelectCommand 还必须要返回这些列才行。此外,将 SqlDataSource 的 DeleteCommand 中的参数名设置为 @ProductID 也是非常重要的。如果没有设置 DataKeyNames 属性,或者参数没有命名为 @ProductsID ,在点击删除按钮只会,虽然还是会有一个回发,但是将不会删除任何行。
图五向我们形象的描述了这个交互过程。可以参考教程 研究插入、更新和删除的关联事件以获取更多的有关通过数据 WEB 控件进行增删改操作所关联的事件链的详细信息。
图五: 在 GridView 中点击了删除按钮之后将调用 SqlDataSource 的 Delete() 方法
第二步:自动生成 INSERT 、 UPDATE 以及 DELETE 语句
在第一步中我们了解到, INSERT 、 UPDATE 以及 DELETE 语句可以通过属性窗口或控件的声明标记代码来进行指定。不过,这需要我们手工编写 SQL 语句,这不仅无聊而且还容易出错。还好,“配置数据源”向导的 “ 指定一个表或视图中的列 ” 页上提供了一个用于自动生成 INSERT 、 UPDATE 以及 DELETE 语句的选项。
好了,我们去看看这个自动生成的选项。在 InsertUpdateDelete.aspx 的设计器中添加一个 DetailsView ,并将其 ID 属性设置为 ManageProducts 。然后,在 DetailsView 的智能标签中选择创建一个新的数据源,并将其命名为 ManageProductsDataSource 。
图六: 创建一个名为 ManageProductsDataSource 的 SqlDataSource
在“配置数据源”中选择 NORTHWINDConnectionString ,然后点击“下一步”。在“配置 Select 语句”页中,选中“指定一个表或视图中的列”单选框,并在下拉框中选择 Products 表。然后在下面的复选框列表中选中 ProductID 、 ProductName 、 UnitPrice 以及 Discontinue 列。
图七: 使用 Products 表,返回其 ProductID 、 ProductName 、 UnitPrice 以及 Discontinue 列
要根据选好的表和列自动生成 INSERT 、 UPDATE 以及 DELETE 语句,点击“高级”按钮并勾上“生成 INSERT 、 UPDATE 以及 DELETE 语句”复选框。
图八: 勾上“生成 INSERT 、 UPDATE 以及 DELETE 语句”复选框
只有当被选择的表含有主键且主键列包含在需要返回的列中时,“生成 INSERT 、 UPDATE 以及 DELETE 语句”复选框才会变为可选状态。在“生成 INSERT 、 UPDATE 以及 DELETE 语句”复选框被选中之后,“使用乐观并发”复选框也会变为可选状态,它将会给最终的 UPDATE 和 DELETE 语句添加一个 WHERE 子句以提供乐观并发控制。不过,现在我们不选它,我们会在下一节中介绍如何通过 SqlDataSource 进行乐观并发控制。
勾上了“生成 INSERT 、 UPDATE 以及 DELETE 语句”复选框之后,点击“确定”以返回“配置 Select 语句”页,然后点击“下一步”,接着点击“完成”以结束“配置数据源”向导。在完成了这个向导之后,Visual Studio 将立刻为 DetailsView 添加一些字段,其中 ProductID 、 ProductName 和 UnitPrice 对应的是 BoundFields , Discontinued 对应的是 CheckBoxField 。在 DetailsView 的智能标签中,选中“允许分页”以使用户在访问这个页面时可以一页一页的查看产品。还有清除DetailsView 的 Width 和 Height 属性。
注意一下,这个智能标签中的“允许插入”、“允许编辑”以及“允许删除”的选项现在已经是可用的了。这是因为SqlDataSource 的 InsertCommand 、 UpdateCommand 以及 DeleteCommand 都含有了相应的 SQL 语句。其声明标记代码如下所示:
1 < asp:DetailsView ID="ManageProducts" runat ="server" AllowPaging ="True"
2 AutoGenerateRows="False" DataKeyNames="ProductID"
3 DataSourceID ="ManageProductsDataSource" EnableViewState ="False">
4 < Fields >
5 < asp:BoundField DataField ="ProductID" HeaderText ="ProductID"
6 InsertVisible="False" ReadOnly="True" SortExpression="ProductID" />
7 < asp:BoundField DataField ="ProductName" HeaderText ="ProductName"
8 SortExpression="ProductName" />
9 < asp:BoundField DataField ="UnitPrice" HeaderText ="UnitPrice"
10 SortExpression="UnitPrice" />
11 < asp:CheckBoxField DataField ="Discontinued" HeaderText ="Discontinued"
12 SortExpression="Discontinued" />
13 Fields >
14 asp:DetailsView >
15
16 < asp:SqlDataSource ID="ManageProductsDataSource" runat ="server"
17 ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
18 DeleteCommand =
19 "DELETE FROM [Products] WHERE [ProductID] = @ProductID"
20 InsertCommand =
21 "INSERT INTO [Products] ([ProductName], [UnitPrice], [Discontinued])
22 VALUES (@ProductName, @UnitPrice, @Discontinued)"
23 SelectCommand =
24 "SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
25 FROM [Products]"
26 UpdateCommand =
27 "UPDATE [Products] SET [ProductName] = @ProductName,
28 [UnitPrice] = @UnitPrice, [Discontinued] = @Discontinued
29 WHERE [ProductID] = @ProductID">
30 < DeleteParameters>
31 < asp:Parameter Name ="ProductID" Type ="Int32" />
32 DeleteParameters>
33 < UpdateParameters>
34 < asp:Parameter Name ="ProductName" Type ="String" />
35 < asp:Parameter Name ="UnitPrice" Type ="Decimal" />
36 < asp:Parameter Name ="Discontinued" Type ="Boolean" />
37 < asp:Parameter Name ="ProductID" Type ="Int32" />
38 UpdateParameters>
39 < InsertParameters>
40 < asp:Parameter Name ="ProductName" Type ="String" />
41 < asp:Parameter Name ="UnitPrice" Type ="Decimal" />
42 < asp:Parameter Name ="Discontinued" Type ="Boolean" />
43 InsertParameters>
44 asp:SqlDataSource >
注意一下 SqlDataSource 控件是如何自动为其 InsertCommand 、 UpdateCommand 以及 DeleteCommand 属性赋值的。 InsertCommand 、 UpdateCommand 以及 DeleteCommand 属性中所引用的列都是根据 SELECT 语句得到的。也就是说, InsertCommand 和 UpdateCommand 将只含有那些在 SelectCommand 中被指定的列,而不是 Products 表的 所有 列(除了 ProductID ,它被忽略是因为它是一个IDENTITY列,而 IDENTITY 列的值是不能修改的,只能在插入时自动赋值)。还有, InsertCommand 、 UpdateCommand 以及 DeleteCommand 属性的每一个参数都存在于相应的 InsertParameters 、 UpdateParameters 以及 DeleteParameters 集合中。
要打开 DetailsView 的数据修改功能,在其智能标签中构上“允许插入”、“允许编辑”以及“允许删除”选项就可以了。当 DetailsView 的 ShowInsertButton 、 ShowEditButton 以及 ShowDeleteButton 属性被设置为 true 时,就会加上相应的 CommandField 了。
现在通过浏览器访问这个页面时,就可以看到 DetailsView 有了 Edit 、 Delete 以及 New 按钮了。点击 Edit 按钮将使 DetailsView 进入编辑模式,这时将把那些 ReadOnly 属性为 false 的 BoundField 显示为 TextBox , CheckBoxField 则显示为 CheckBox 。
图九: DetailsView 默认的编辑界面
同样,这里你也可以删除选中的产品或添加一个新的产品到系统中。由于 InsertCommand 只有 ProductName 、 UnitPrice 和 Discontinued 列,所以在向数据库插入记录时其他的列将为 NULL 或该列的默认值。跟 ObjectDataSource 一样,如果 InsertCommand 遇到某个拥有不允许为空又没有默认值的字段的表,在试图执行 INSERT 语句的时候就会引发一个SQL 错误。
注意: DetailsView 的插入和编辑界面没有任何自定义或验证能力。要添加验证控件或是要自定义界面,你需要将BoundField 转换为 TemplateField 。具体的办法可以参见 给编辑和新增界面增加验证控件和 定制数据修改界面。对了,要记住 DetailsView 在更新和删除时使用的是当前产品的 DataKey ,而这个 DataKey 仅在配置了 DataKeyNames 属性之后才能用。如果编辑或删除操作没有任何效果,那么你就要看看是不是没有设置 DataKeyNames 属性了。
自动生成 SQL 语句的局限性
由于“生成 INSERT 、 UPDATE 以及 DELETE 语句”选项仅在从一个表中选择了一些列之后才可用,如果要弄一个复杂些的查询,那么你就不得不像我们在第一步中做的那样,自己编写 INSERT 、 UPDATE 以及 DELETE 语句。通常,为了显示的需要, SELECT 语句会使用 JOIN 来从一个或多个子表中查找数据(比如在显示产品信息时,我们还需要从 Categories 表中得到相应 CategoryName 字段)。但同时我们可能仍然希望允许用户编辑、更新或插入(译者注:原文就是这样的,实际上应该是增删改操作)数据到“核心”表中(这里就是 Products 表)。
虽然可以通过手工方式输入 INSERT 、 UPDATE 以及 DELETE 语句,但还是请考虑一下下面的节约时间的小贴士。首先将 SqlDataSource 设置为只仅可以返回 Products 表中的数据。使用“配置数据源”向导的“指定一个表或视图的列”页,这样你就可以自动的创建INSERT 、 UPDATE 以及 DELETE 语句。在完成了这个向导之后,再从属性窗口中配置 SelectQuery (或者回到“配置数据源”向导,只不过这次使用“指定一个自定义 SQL 语句或存储过程”)。然后修改 SELECT 语句以给它加上 JOIN 。这样,你既通过自动生成 SQL 语句而节约了时间,又可以自定义 SELECT 语句以使其更加复杂。
自动生成 INSERT 、 UPDATE 以及 DELETE 语句的另一个限制是,它们的列 ( 只有 INSERT 和 UPDATE ,这里不关 DELETE 的事 ) 都是基于 SELECT 语句所返回的列的。然而,我们可能需要更新或插入更多或更少的字段。比如在第二步的例子中,我们可能希望UnitPrice BoundField 是只读的,这时它就不应该出现在 UpdateCommand 中。或者我们可能希望给表中某个并没有显示在 GridView 中的字段设置一个值,比如当添加一个新记录时,我们可能希望把QuantityPerUnit 设置为 “TODO” 。
如果有这样的自定义需求,你就需要通过属性窗口,或是向导中的“指定一个自定义SQL 语句或存储过程”选项,或是声明标记代码来手工的完成这些工作。
注意: 当添加一个在数据 Web 控件中没有相应字段的参数时,记住一定要通过某种方式为这个参数赋值。这个参数可以:直接在 InsertCommand 或 UpdateCommand 中硬编码;来自某个预定义的源( QueryString 、 Session 、 Web 控件……);或是像我们在前一节教程中看到的那样通过编程的方式来进行赋值。
总结
为了让数据 Web 控件可以利用其内建的插入、编辑以及删除功能,它们所绑定的数据源就必须实现相应的功能。对于SqlDataSource 来说,这就意味着必须把 INSERT 、 UPDATE 以及 DELETE 语句赋值给 InsertCommand 、 UpdateCommand 以及 DeleteCommand 属性。这些属性及其相关的参数集合可以手工添加也可以在“配置数据源”向导中自动生成。本教程中,两种办法我们都介绍过了。
在 实现开放式并发中,我们介绍了如何通过 ObjectDataSource 实现乐观并发(译者注:开放式并发就是乐观并发,相应的就是保守式并发和悲观并发)。SqlDataSource 控件同样也提供了对乐观并发的支持。在第二步中我们注意到,当自动生成 INSERT 、 UPDATE 以及 DELETE 语句时,向导提供了一个“使用乐观并发”的选项。我们将在下一节教程中看到,在 SqlDataSource 中使用乐观并发将会修改 UPDATE 和 DELETE 语句的 WHERE 子句以保证从上次在本页显示数据到现在为止,其他列的值都没有被修改。