在ASP.NET 2.0中操作数据:使用SqlDataSource插入、更新以及删除数据
英文原版 | 本教程的代码(C#) | 翻译目录 | 原文目录
导言
在概述插入、更新和删除数据中我们讨论过,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命令”输入框中填上如下代码(你可以手工填写,也可以使用查询生成器,反正随你高兴):
2 WHERE ProductID = @ProductID
然后,点击“刷新参数”按钮以将@ProductID参数添加到下面的参数列表中。
图三:命令和参数编辑器(译者注:原文错了,“Select the DeleteQuery Property from the Properties Window”,这是图二的描述)
不要给这个参数赋值(保留其参数源为“无”)。在我们向GridView添加了删除支持后,GridView将通过其DataKeys集合为按下了删除按钮的那一行自动提供这个参数值。
注意:DELETE语句中使用的参数名必须跟GridView、DetailsView或FormView的DataKeyNames相同。也就是说,我们专门把DELETE中的那个参数命名为@ProductID(而不是@ID),是因为Products表中主键列的名称为ProductID(因此GridView的DataKeyNames值也是这个)。如果参数名与DataKeyNames不同,GridView将无法自动通过其DataKeys集合为这个参数赋值。
在“命令和参数编辑器”对话框中输入了与删除相关的信息之后,点击“确定”,并去到源视图中看看现在的声明标记代码:
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属性和<DeleteParameters>节,还有名为productID 的Parameter对象。
配置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语句。其声明标记代码如下所示:
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子句以保证从上次在本页显示数据到现在为止,其他列的值都没有被修改。
编程愉快!
关于作者
Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用微软Web技术。Scott是个独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。