(VB) 插入、更新和删除数据的概述
本教程介绍如何将 ObjectDataSource 的 Insert () 、Update () 和 Delete () 方法映射到 BLL 类的方法,以及如何配置 GridView、DetailsView 和 FormView 控件以提供数据修改功能。
简介
在过去的几个教程中,我们了解了如何使用 GridView、DetailsView 和 FormView 控件在 ASP.NET 页面中显示数据。 这些控件只处理提供给它们的数据。 通常,这些控制通过使用数据源控件(如 ObjectDataSource)来访问数据。 我们已了解 ObjectDataSource 如何充当 ASP.NET 页和基础数据之间的代理。 当 GridView 需要显示数据时,它会调用其 ObjectDataSource 的 Select()
方法,该方法又从业务逻辑层 (BLL) 调用方法,该方法调用相应数据访问层的 (DAL) TableAdapter 中的方法,后者反过来将查询发送到 SELECT
Northwind 数据库。
回想一下,当我们 在第一个教程的 DAL 中创建 TableAdapter 时,Visual Studio 会自动添加用于从基础数据库表插入、更新和删除数据的方法。 此外,在 创建业务逻辑层 中,我们在 BLL 中设计了调用这些数据修改 DAL 方法的方法。
除了其 Select()
方法之外,ObjectDataSource 还具有 Insert()
、 Update()
和 Delete()
方法。 与 方法一 Select()
样,这三种方法可以映射到基础对象中的方法。 当配置为插入、更新或删除数据时,GridView、DetailsView 和 FormView 控件提供用于修改基础数据的用户界面。 此用户界面调用 Insert()
ObjectDataSource 的 、 Update()
和 Delete()
方法,然后调用基础对象的关联方法 (见图 1) 。
图 1:ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法充当 BLL 中的代理 (单击以查看全尺寸图像)
本教程介绍如何将 ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法映射到 BLL 中类的方法,以及如何配置 GridView、DetailsView 和 FormView 控件以提供数据修改功能。
步骤 1:创建插入、更新和删除教程网页
在开始探索如何插入、更新和删除数据之前,让我们先花点时间在网站项目中创建 ASP.NET 页面,本教程和后续几个页面将需要这些页面。 首先添加名为 EditInsertDelete
的新文件夹。 接下来,将以下 ASP.NET 页添加到该文件夹,确保将每个页面与 Site.master
母版页相关联:
Default.aspx
Basics.aspx
DataModificationEvents.aspx
ErrorHandling.aspx
UIValidation.aspx
CustomizedUI.aspx
OptimisticConcurrency.aspx
ConfirmationOnDelete.aspx
UserLevelAccess.aspx
图 2:为数据 Modification-Related 教程添加 ASP.NET 页
与其他文件夹中一样, Default.aspx
文件夹中 EditInsertDelete
会列出其部分中的教程。 回想一下, SectionLevelTutorialListing.ascx
用户控件提供了此功能。 因此,通过将用户控件从解决方案资源管理器拖动到Default.aspx
页面的设计视图中,将此用户控件添加到 。
图 3:将 SectionLevelTutorialListing.ascx
用户控件添加到 Default.aspx
(单击以查看全尺寸图像)
最后,将页面作为条目添加到文件中 Web.sitemap
。 具体而言,在“自定义格式” <siteMapNode>
后面添加以下标记:
<siteMapNode title="Editing, Inserting, and Deleting" url="~/EditInsertDelete/Default.aspx" description="Samples of Reports that Provide Editing, Inserting, and Deleting Capabilities"> <siteMapNode url="~/EditInsertDelete/Basics.aspx" title="Basics" description="Examines the basics of data modification with the GridView, DetailsView, and FormView controls." /> <siteMapNode url="~/EditInsertDelete/DataModificationEvents.aspx" title="Data Modification Events" description="Explores the events raised by the ObjectDataSource pertinent to data modification." /> <siteMapNode url="~/EditInsertDelete/ErrorHandling.aspx" title="Error Handling" description="Learn how to gracefully handle exceptions raised during the data modification workflow." /> <siteMapNode url="~/EditInsertDelete/UIValidation.aspx" title="Adding Data Entry Validation" description="Help prevent data entry errors by providing validation." /> <siteMapNode url="~/EditInsertDelete/CustomizedUI.aspx" title="Customize the User Interface" description="Customize the editing and inserting user interfaces." /> <siteMapNode url="~/EditInsertDelete/OptimisticConcurrency.aspx" title="Optimistic Concurrency" description="Learn how to help prevent simultaneous users from overwritting one another s changes." /> <siteMapNode url="~/EditInsertDelete/ConfirmationOnDelete.aspx" title="Confirm On Delete" description="Prompt a user for confirmation when deleting a record." /> <siteMapNode url="~/EditInsertDelete/UserLevelAccess.aspx" title="Limit Capabilities Based on User" description="Learn how to limit the data modification functionality based on the user role or permissions." /> </siteMapNode>
更新 Web.sitemap
后,请花点时间通过浏览器查看教程网站。 左侧菜单现在包含用于编辑、插入和删除教程的项目。
图 4:站点地图现在包含编辑、插入和删除教程的条目
步骤 2:添加和配置 ObjectDataSource 控件
由于 GridView、DetailsView 和 FormView 在数据修改功能和布局上各有不同,因此让我们单独检查一下。 但是,我们不要让每个控件使用其自己的 ObjectDataSource,而只需创建一个所有三个控件示例都可以共享的 ObjectDataSource。
Basics.aspx
打开页面,将 ObjectDataSource 从工具箱拖到Designer,然后单击其智能标记中的“配置数据源”链接。 ProductsBLL
由于 是唯一提供编辑、插入和删除方法的 BLL 类,因此请将 ObjectDataSource 配置为使用此类。
图 5:将 ObjectDataSource 配置为使用 ProductsBLL
类 (单击以查看全尺寸图像)
在下一个屏幕中,我们可以指定类的 ProductsBLL
哪些方法映射到 ObjectDataSource 的 Select()
、 Insert()
、 Update()
和 Delete()
,方法是选择相应的选项卡并从下拉列表中选择方法。 图 6(现在看起来应该很熟悉)将 ObjectDataSource 的 Select()
方法映射到 ProductsBLL
类的 GetProducts()
方法。 Insert()
可以通过从顶部列表中选择相应的选项卡来配置 、 Update()
和 Delete()
方法。
图 6:让 ObjectDataSource 返回所有产品 (单击以查看全尺寸图像)
图 7、8 和 9 显示了 ObjectDataSource 的 UPDATE、INSERT 和 DELETE 选项卡。 配置这些选项卡, Insert()
以便 、 Update()
和 Delete()
方法分别调用 ProductsBLL
类的 UpdateProduct
、 AddProduct
和 DeleteProduct
方法。
图 7:将 ObjectDataSource 的 Update()
方法映射到 ProductBLL
类的方法 UpdateProduct
(单击以查看全尺寸图像)
图 8:将 ObjectDataSource 的 Insert()
方法映射到 ProductBLL
类的 Add Product
方法 (单击以查看全尺寸图像)
图 9:将 ObjectDataSource 的 Delete()
方法映射到 ProductBLL
类 DeleteProduct
的方法 (单击以查看全尺寸图像)
你可能已注意到,“更新”、“插入”和“删除”选项卡中的下拉列表已选中这些方法。 这要归功于我们使用 DataObjectMethodAttribute
修饰 方法的 ProductsBLL
。 例如,DeleteProduct 方法具有以下签名:
<System.ComponentModel.DataObjectMethodAttribute _ (System.ComponentModel.DataObjectMethodType.Delete, True)> _ Public Function DeleteProduct(ByVal productID As Integer) As Boolean End Function
属性 DataObjectMethodAttribute
指示每个方法的用途,无论是用于选择、插入、更新还是删除,以及它是否为默认值。 如果在创建 BLL 类时省略了这些属性,则需要从“UPDATE”、“INSERT”和“DELETE”选项卡中手动选择方法。
确保将适当的 ProductsBLL
方法映射到 ObjectDataSource 的 Insert()
、 Update()
和 Delete()
方法后,单击“完成”以完成向导。
检查 ObjectDataSource 的标记
通过向导配置 ObjectDataSource 后,转到“源”视图以检查生成的声明性标记。 标记 <asp:ObjectDataSource>
指定要调用的基础对象和方法。 此外,还有 、 和 ,它们映射到类的 、 UpdateProduct
和 DeleteProduct
方法的AddProduct
输入参数ProductsBLL
:InsertParameters
UpdateParameters
DeleteParameters
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" DeleteMethod="DeleteProduct" InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL" UpdateMethod="UpdateProduct"> <DeleteParameters> <asp:Parameter Name="productID" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> <asp:Parameter Name="productID" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="productName" Type="String" /> <asp:Parameter Name="supplierID" Type="Int32" /> <asp:Parameter Name="categoryID" Type="Int32" /> <asp:Parameter Name="quantityPerUnit" Type="String" /> <asp:Parameter Name="unitPrice" Type="Decimal" /> <asp:Parameter Name="unitsInStock" Type="Int16" /> <asp:Parameter Name="unitsOnOrder" Type="Int16" /> <asp:Parameter Name="reorderLevel" Type="Int16" /> <asp:Parameter Name="discontinued" Type="Boolean" /> </InsertParameters> </asp:ObjectDataSource>
ObjectDataSource 包含其关联方法的每个输入参数的参数,就像将 ObjectDataSource 配置为调用需要输入参数 ((如 GetProductsByCategoryID(categoryID)
) )的 select 方法时,存在 一个 列表SelectParameter
一样。 稍后我们将看到,在调用 ObjectDataSource 的 、 或 方法之前,GridView、DetailsView 和 FormView 会自动设置这些 DeleteParameters
UpdateParameters
、 Update()
和 Delete()
的值Insert()
。InsertParameters
还可以根据需要以编程方式设置这些值,我们将在后面的教程中讨论。
使用向导配置 ObjectDataSource 的一个副作用是,Visual Studio 将 OldValuesParameterFormatString 属性 设置为 original_{0}
。 此属性值用于包括正在编辑的数据的原始值,在两种情况下非常有用:
- 如果在编辑记录时,用户可以更改主键值。 在这种情况下,必须同时提供新的主键值和原始主键值,以便可以找到具有原始主键值的记录并相应地更新其值。
- 使用乐观并发时。 乐观并发是一种确保两个用户同时不覆盖彼此更改的技术,是未来教程的主题。
属性 OldValuesParameterFormatString
指示基础对象的更新和删除方法中原始值的输入参数的名称。 在探索乐观并发时,我们将更详细地讨论此属性及其用途。 但是,我现在提出它,因为我们的 BLL 方法不需要原始值,因此删除此属性很重要。 OldValuesParameterFormatString
当数据 Web 控件尝试调用 ObjectDataSource 的 Update()
或 Delete()
方法时,将属性设置为默认 ({0}
) 以外的任何值都会导致错误,因为 ObjectDataSource 将尝试传入UpdateParameters
指定的 或 DeleteParameters
以及原始值参数。
如果目前还不清楚这一点,请不要担心,我们将在将来的教程中检查此属性及其实用工具。 现在,只需确保从声明性语法中完全删除此属性声明,或将值设置为默认值 ({0}) 。
注意
如果只是从“设计”视图中的属性窗口清除OldValuesParameterFormatString
属性值,则属性仍将存在于声明性语法中,但会设置为空字符串。 遗憾的是,这仍会导致上面讨论的相同问题。 因此,请从声明性语法中完全删除 属性,或者从属性窗口将 值设置为默认值 {0}
。
步骤 3:添加数据 Web 控件并对其进行配置以用于数据修改
将 ObjectDataSource 添加到页面并配置后,我们就可以将数据 Web 控件添加到页面,以便显示数据并为最终用户提供修改数据的方法。 我们将分别查看 GridView、DetailsView 和 FormView,因为这些数据 Web 控件在数据修改功能和配置方面有所不同。
如本文的其余部分所示,通过 GridView、DetailsView 和 FormView 控件添加非常基本的编辑、插入和删除支持与选中几个复选框一样简单。 在现实世界中,有许多细微之处和边缘情况,使得提供此类功能不仅仅是点和单击。 但是,本教程仅重点介绍如何证明简单的数据修改功能。 未来的教程将探讨在真实环境中无疑会出现的问题。
从 GridView 中删除数据
首先将 GridView 从工具箱拖动到Designer。 接下来,通过从 GridView 的智能标记中的下拉列表中选择 ObjectDataSource,将 ObjectDataSource 绑定到 GridView。 此时,GridView 的声明性标记将为:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> </Columns> </asp:GridView>
通过其智能标记将 GridView 绑定到 ObjectDataSource 有两个好处:
- BoundFields 和 CheckBoxFields 会自动为 ObjectDataSource 返回的每个字段创建。 此外,BoundField 和 CheckBoxField 的属性是根据基础字段的元数据设置的。 例如,
ProductID
、CategoryName
和SupplierName
字段在 中ProductsDataTable
标记为只读,因此在编辑时不应更新。 为了适应这种情况,这些 BoundFields 的 ReadOnly 属性 设置为True
。 - DataKeyNames 属性分配给基础对象的主键字段 () 。 使用 GridView 编辑或删除数据时,这一点至关重要,因为此属性指示唯一标识每条记录的字段 (或字段集) 。 有关 属性的详细信息
DataKeyNames
,请参阅 使用带有 Details DetailView 的可选主网格视图的母版/详细信息 教程。
虽然 GridView 可以通过属性窗口或声明性语法绑定到 ObjectDataSource,但这样做需要手动添加适当的 BoundField 和DataKeyNames
标记。
GridView 控件提供对行级编辑和删除的内置支持。 将 GridView 配置为支持删除会添加一列“删除”按钮。 当最终用户单击特定行的“删除”按钮时,将发生回发,GridView 将执行以下步骤:
- 分配 ObjectDataSource 的值
DeleteParameters
() - 调用 ObjectDataSource 的
Delete()
方法,删除指定的记录 - GridView 通过调用其
Select()
方法将自身重新绑定到 ObjectDataSource
分配给 DeleteParameters
的值是单击了“删除”按钮的行 () 字段的值 DataKeyNames
。 因此,正确设置 GridView 的 DataKeyNames
属性至关重要。 如果缺少, DeleteParameters
将在步骤 1 中为 赋值 Nothing
,这反过来又不会导致步骤 2 中的任何记录被删除。
注意
集合 DataKeys
存储在 GridView 控件状态中,这意味着 DataKeys
即使 GridView 视图状态已禁用,也会在回发时记住这些值。 但是,对于支持编辑或删除的 GridView,视图状态保持启用状态非常重要, (默认行为) 。 如果将 GridView 的 EnableViewState
属性设置为 false
,则编辑和删除行为对于单个用户而言将正常工作,但如果存在并发用户删除数据,则这些并发用户可能会意外删除或编辑他们无意的记录。
此警告也适用于 DetailsViews 和 FormViews。
若要将删除功能添加到 GridView,只需转到其智能标记并检查“启用删除”复选框。
图 10:选中“启用删除”复选框
选中智能标记中的“启用删除”复选框会将 CommandField 添加到 GridView。 CommandField 在 GridView 中呈现一列,其中包含用于执行以下一项或多项任务的按钮:选择记录、编辑记录和删除记录。 之前,我们看到了 CommandField 在 大纲/详细信息中使用带详细信息详细信息视图的可选主网格视图 教程中选择记录的操作。
CommandField 包含许多 ShowXButton
属性,这些属性指示 CommandField 中显示的按钮系列。 通过选中“启用删除”复选框,其属性True
已添加到 GridView 的 Columns 集合的 CommandFieldShowDeleteButton
。
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>
在这一点上,信不信由你,我们已经完成了向 GridView 添加删除支持! 如图 11 所示,通过浏览器访问此页面时,存在一列“删除”按钮。
图 11:CommandField 添加删除按钮列 (单击以查看全尺寸图像)
如果你一直在自行构建本教程,在测试此页面时,单击“删除”按钮将引发异常。 继续阅读,了解引发这些异常的原因以及如何修复它们。
注意
如果你使用本教程随附的下载进行跟踪,则这些问题已经解决。 但是,我建议你通读下面列出的详细信息,以帮助确定可能出现的问题和合适的解决方法。
如果在尝试删除产品时收到异常,其消息类似于“ObjectDataSource 'ObjectDataSource1' 找不到具有参数的非泛型方法'DeleteProduct',original_ProductID”,则可能忘记从 ObjectDataSource 中删除属性 OldValuesParameterFormatString
。 OldValuesParameterFormatString
指定 属性后,ObjectDataSource 尝试将 和 original_ProductID
输入参数DeleteProduct
传入productID
方法。 DeleteProduct
但是,只接受单个输入参数,因此异常。 删除 OldValuesParameterFormatString
属性 (或将其设置为 {0}
) 指示 ObjectDataSource 不尝试传入原始输入参数。
图 12:确保 OldValuesParameterFormatString
已清除属性 (单击以查看全尺寸图像)
即使删除OldValuesParameterFormatString
了 属性,尝试删除产品时仍会收到异常,并显示以下消息:“DELETE 语句与 REFERENCE 约束'FK_Order_Details_Products'冲突。”Northwind 数据库包含 和 表之间的Order Details
外键约束,这意味着,如果表中有一个或多个产品记录,则无法从系统中删除产品Order Details
。Products
由于 Northwind 数据库中的每一个产品在 中 Order Details
至少有一条记录,因此在首次删除产品的关联订单详细信息记录之前,无法删除任何产品。
图 13:外键约束禁止删除产品 (单击以查看全尺寸图像)
对于我们的教程,让我们从 Order Details
表中删除所有记录。 在实际应用程序中,我们需要:
- 有另一个屏幕来管理订单详细信息
- 扩充 方法以
DeleteProduct
包含删除指定产品的订单详细信息的逻辑 - 修改 TableAdapter 使用的 SQL 查询,以包括删除指定产品的订单详细信息
让我们从表中删除所有记录, Order Details
以绕过外键约束。 转到 Visual Studio 中的“服务器资源管理器”,右键单击 NORTHWND.MDF
节点,然后选择“新建查询”。 然后,在查询窗口中运行以下 SQL 语句: DELETE FROM [Order Details]
图 14:从 Order Details
表中删除所有记录 (单击以查看全尺寸图像)
清除表后, Order Details
单击“删除”按钮将删除产品,而不会出错。 如果单击“删除”按钮未删除产品,检查以确保 GridView 的 DataKeyNames
属性设置为主键字段 (ProductID
) 。
注意
单击“删除”按钮时,会发出回发,记录将被删除。 这很危险,因为很容易意外单击错误的行的“删除”按钮。 在以后的教程中,我们将了解如何在删除记录时添加客户端确认。
使用 GridView 编辑数据
除了删除,GridView 控件还提供内置的行级编辑支持。 配置 GridView 以支持编辑会添加一列“编辑”按钮。 从最终用户的角度来看,单击行的“编辑”按钮会使该行变为可编辑,将单元格转换为包含现有值的文本框,并将“编辑”按钮替换为“更新”和“取消”按钮。 进行所需更改后,最终用户可以单击“更新”按钮提交更改,或单击“取消”按钮放弃更改。 在任一情况下,单击“更新”或“取消”后,GridView 将返回到其预编辑状态。
从页面开发人员的角度来看,当最终用户单击特定行的“编辑”按钮时,就会发生回发,GridView 将执行以下步骤:
- GridView 的
EditItemIndex
属性分配给单击了“编辑”按钮的行的索引 - GridView 通过调用其
Select()
方法将自身重新绑定到 ObjectDataSource - 与 匹配的
EditItemIndex
行索引以“编辑模式”呈现。在此模式下,“编辑”按钮将替换为“更新”和“取消”按钮以及属性为 False 的 BoundFieldsReadOnly
, (默认) 呈现为 TextBox Web 控件,其Text
属性分配给数据字段的值。
此时,标记将返回到浏览器,使最终用户能够对行的数据进行任何更改。 当用户单击“更新”按钮时,会发生回发,GridView 将执行以下步骤:
- ObjectDataSource 的值
UpdateParameters
() 分配最终用户在 GridView 的编辑界面中输入的值 - 调用 ObjectDataSource 的
Update()
方法,更新指定的记录 - GridView 通过调用其
Select()
方法将自身重新绑定到 ObjectDataSource
在步骤 1 中分配给 UpdateParameters
的主键值来自 属性中指定的 DataKeyNames
值,而非主键值来自已编辑行的 TextBox Web 控件中的文本。 与删除一样,正确设置 GridView 的 DataKeyNames
属性至关重要。 如果缺少主键值, UpdateParameters
将在步骤 1 中为主键值分配值 Nothing
,这反过来又不会导致步骤 2 中的任何更新记录。
只需选中 GridView 的智能标记中的“启用编辑”复选框即可激活编辑功能。
图 15:选中“启用编辑”复选框
选中“启用编辑”复选框将根据需要添加 CommandField () 并将其属性设置为 ShowEditButton
True
。 如前所述,CommandField 包含许多 ShowXButton
属性,这些属性指示 CommandField 中显示的按钮系列。 选中“启用编辑”复选框会将 ShowEditButton
属性添加到现有的 CommandField:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1"> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> ... BoundFields removed for brevity ... </Columns> </asp:GridView>
这就是添加基础编辑支持的全部内容。 如图 16 所示,每个 BoundField 的编辑界面相当粗糙,其 ReadOnly
属性设置为 False
(默认) 呈现为 TextBox。 这包括和 SupplierID
等CategoryID
字段,它们是其他表的键。
图 16:单击“Chai”的“编辑”按钮在编辑模式下显示行 (单击以查看全尺寸图像)
除了要求用户直接编辑外键值外,编辑界面的界面还缺乏以下方面:
- 如果用户输入
CategoryID
数据库中不存在的 或SupplierID
,UPDATE
将违反外键约束,从而导致引发异常。 - 编辑界面不包含任何验证。 如果未提供所需的值 ((如
ProductName
) ),或者输入应为数值的字符串值 (如在UnitPrice
文本框中输入“太多!”) ,则会引发异常。 未来的教程将介绍如何将验证控件添加到编辑用户界面。 - 目前, 所有 非只读产品字段都必须包含在 GridView 中。 如果我们要从 GridView 中删除字段,例如
UnitPrice
,在更新数据时,GridView 不会设置UnitPrice
UpdateParameters
值,这会将数据库记录的UnitPrice
更改为NULL
值。 同样,如果从 GridView 中删除必填字段(如ProductName
),更新将失败,并出现上述相同的“列'ProductName'不允许 null”异常。 - 编辑界面格式留下了很多需要。
UnitPrice
显示有四个小数点的 。 理想情况下,CategoryID
和SupplierID
值将包含列出系统中类别和供应商的 DropDownList。
这些都是我们目前必须面对的缺点,但将在将来的教程中解决。
使用 DetailsView 插入、编辑和删除数据
正如我们在前面的教程中看到的那样,DetailsView 控件一次显示一条记录,并且与 GridView 一样,允许编辑和删除当前显示的记录。 最终用户从 DetailsView 编辑和删除项目以及从 ASP.NET 端工作流的体验与 GridView 的体验相同。 DetailsView 与 GridView 的不同之处在于它还提供内置的插入支持。
若要演示 GridView 的数据修改功能,请首先将 DetailsView 添加到 Basics.aspx
现有 GridView 上方的页面,并通过 DetailsView 的智能标记将其绑定到现有 ObjectDataSource。 接下来,清除 DetailsView 的 Height
和 Width
属性,并从智能标记中检查“启用分页”选项。 若要启用编辑、插入和删除支持,只需检查智能标记中的“启用编辑”、“启用插入”和“启用删除”复选框即可。
图 17:配置 DetailsView 以支持编辑、插入和删除
与 GridView 一样,添加编辑、插入或删除支持会将 CommandField 添加到 DetailsView,如以下声明性语法所示:
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <Fields> <asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False" ReadOnly="True" SortExpression="ProductID" /> <asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" /> <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" /> <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" /> <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" SortExpression="QuantityPerUnit" /> <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" SortExpression="UnitPrice" /> <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" SortExpression="UnitsInStock" /> <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" /> <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" SortExpression="ReorderLevel" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> <asp:BoundField DataField="CategoryName" HeaderText="CategoryName" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="SupplierName" ReadOnly="True" SortExpression="SupplierName" /> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowInsertButton="True" /> </Fields> </asp:DetailsView>
请注意,对于 DetailsView,CommandField 默认显示在 Columns 集合的末尾。 由于 DetailsView 的字段呈现为行,因此 CommandField 显示为一行,详细信息视图底部有“插入”、“编辑”和“删除”按钮。
图 18:将 DetailsView 配置为支持编辑、插入和删除 (单击以查看全尺寸图像)
单击“删除”按钮启动与 GridView 相同的事件序列:回发;然后是 DetailsView,根据DataKeyNames
值填充其 ObjectDataSource 的 DeleteParameters
;并完成调用其 ObjectDataSource 的 Delete()
方法,这实际上从数据库中删除了产品。 在 DetailsView 中编辑的工作方式与 GridView 相同。
对于插入,最终用户会看到一个“新建”按钮,单击该按钮时,在“插入模式”中呈现 DetailsView。使用“插入模式”时,“新建”按钮将替换为“插入”和“取消”按钮,并且仅显示属性设置为 True
(默认) 的 BoundFieldInsertVisible
。 标识为自动递增字段的数据字段(如 ProductID
)在通过智能标记将 DetailsView 绑定到数据源时,其 InsertVisible 属性 设置为 False
。
通过智能标记将数据源绑定到 DetailsView 时,Visual Studio 仅为自动递增字段将 属性设置为 InsertVisible
False
。 只读字段(如 CategoryName
和 SupplierName
)将在“插入模式”用户界面中显示,除非其 InsertVisible
属性显式设置为 False
。 花点时间将这两个字段的属性 InsertVisible
设置为 False
,无论是通过 DetailsView 的声明性语法,还是通过智能标记中的“编辑字段”链接。 图 19 显示通过单击“编辑字段”链接将False
属性设置为 InsertVisible
。
图 19:Northwind Traders 现在提供 Acme 茶 (单击以查看全尺寸图像)
设置 InsertVisible
属性后,在浏览器中查看 Basics.aspx
页面,然后单击“新建”按钮。 图 20 显示了向我们的产品系列添加新饮料 Acme Tea 时的详细信息视图。
图 20:Northwind Traders 现在提供 Acme 茶 (单击以查看全尺寸图像)
输入 Acme Tea 的详细信息并单击“插入”按钮后,将进行回发,并将新记录添加到 Products
数据库表。 由于此 DetailsView 按在数据库表中存在的顺序列出产品,因此我们必须分页到最后一个产品才能查看新产品。
图 21:Acme Tea (详细信息 单击以查看全尺寸图像)
注意
DetailsView 的 CurrentMode 属性 指示正在显示的接口,可以是以下值之一: Edit
、 Insert
或 ReadOnly
。 DefaultMode 属性指示完成编辑或插入后 DetailsView 返回到的模式,并且可用于显示永久处于编辑或插入模式的 DetailsView。
DetailsView 的点击插入和编辑功能受到与 GridView 相同的限制:用户必须通过文本框输入现有 CategoryID
值和 SupplierID
值;接口缺少任何验证逻辑;不允许值或在数据库级别没有指定默认值的所有产品字段 NULL
都必须包含在插入接口中, 等等。
我们将在未来的文章中研究用于扩展和增强 GridView 编辑界面的技术也可以应用于 DetailsView 控件的编辑和插入界面。
使用 FormView 实现更灵活的数据修改用户界面
FormView 提供插入、编辑和删除数据的内置支持,但由于它使用模板而不是字段,因此无法添加 GridView 和 DetailsView 控件使用的 BoundFields 或 CommandField 来提供数据修改接口。 相反,此界面必须将用于在添加新项或编辑现有项时收集用户输入的 Web 控件以及“新建”、“编辑”、“删除”、“插入”、“更新”和“取消”按钮手动添加到相应的模板。 幸运的是,通过智能标记中的下拉列表将 FormView 绑定到数据源时,Visual Studio 会自动创建所需的接口。
为了说明这些技术,首先将 FormView 添加到 Basics.aspx
页面,然后从 FormView 的智能标记将其绑定到已创建的 ObjectDataSource。 这将为包含 TextBox Web 控件的 FormView 生成 EditItemTemplate
、 InsertItemTemplate
、 和 ItemTemplate
,用于收集用户的输入,并为“新建”、“编辑”、“删除”、“插入”、“更新”和“取消”按钮生成按钮 Web 控件。 此外,FormView 的 DataKeyNames
属性设置为 objectDataSource 返回的对象 (ProductID
) 的主键字段。 最后,检查 FormView 的智能标记中的“启用分页”选项。
下面显示了 FormView 绑定到 ObjectDataSource 后 FormView 的 ItemTemplate
声明性标记。 默认情况下,每个非布尔值产品字段都绑定到 Text
Label Web 控件的 属性,而 () Discontinued
的每个布尔值字段都绑定到 Checked
禁用的 CheckBox Web 控件的 属性。 为了使“新建”、“编辑”和“删除”按钮在单击时触发某些 FormView 行为,必须分别将其 CommandName
值设置为 New
、 Edit
和 Delete
。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ProductID: <asp:Label ID="ProductIDLabel" runat="server" Text='<%# Eval("ProductID") %>'></asp:Label><br /> ProductName: <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Bind("ProductName") %>'> </asp:Label><br /> SupplierID: <asp:Label ID="SupplierIDLabel" runat="server" Text='<%# Bind("SupplierID") %>'> </asp:Label><br /> CategoryID: <asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Bind("CategoryID") %>'> </asp:Label><br /> QuantityPerUnit: <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Bind("QuantityPerUnit") %>'> </asp:Label><br /> UnitPrice: <asp:Label ID="UnitPriceLabel" runat="server" Text='<%# Bind("UnitPrice") %>'></asp:Label><br /> UnitsInStock: <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Bind("UnitsInStock") %>'> </asp:Label><br /> UnitsOnOrder: <asp:Label ID="UnitsOnOrderLabel" runat="server" Text='<%# Bind("UnitsOnOrder") %>'> </asp:Label><br /> ReorderLevel: <asp:Label ID="ReorderLevelLabel" runat="server" Text='<%# Bind("ReorderLevel") %>'> </asp:Label><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked='<%# Bind("Discontinued") %>' Enabled="false" /><br /> CategoryName: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Bind("CategoryName") %>'> </asp:Label><br /> SupplierName: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Bind("SupplierName") %>'> </asp:Label><br /> <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit" Text="Edit"> </asp:LinkButton> <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete"> </asp:LinkButton> <asp:LinkButton ID="NewButton" runat="server" CausesValidation="False" CommandName="New" Text="New"> </asp:LinkButton> </ItemTemplate> </asp:FormView>
图 22 显示了通过浏览器查看 FormView 的 ItemTemplate
。 每个产品字段都列出,底部有“新建”、“编辑”和“删除”按钮。
图 22:Defaut FormView ItemTemplate
Lists每个产品字段以及“新建”、“编辑”和“删除”按钮 (单击以查看全尺寸图像)
与 GridView 和 DetailsView 一样,单击“删除”按钮或任何属性设置为 Delete 的 CommandName
Button、LinkButton 或 ImageButton 会导致回发,根据 FormView 的值填充 ObjectDataSource DeleteParameters
的 DataKeyNames
,并调用 ObjectDataSource 的 Delete()
方法。
单击“编辑”按钮时,会随之回发,并且数据将反弹到 EditItemTemplate
,后者负责呈现编辑界面。 此接口包括用于编辑数据的 Web 控件以及“更新”和“取消”按钮。 Visual Studio 生成的默认值 EditItemTemplate
包含用于) (任何自动递增字段 ProductID
的标签、每个非布尔值字段的 TextBox 和每个布尔值字段的 CheckBox。 此行为与 GridView 和 DetailsView 控件中自动生成的 BoundField 非常相似。
注意
FormView 自动生成 的 EditItemTemplate
一个小问题是,它为只读字段(如 CategoryName
和 SupplierName
)呈现 TextBox Web 控件。 我们稍后将了解如何对此进行解释。
中的 EditItemTemplate
TextBox 控件使用双向数据绑定将其Text
属性绑定到相应数据字段的值。 双向数据绑定(由 <%# Bind("dataField") %>
表示)在将数据绑定到模板和填充 ObjectDataSource 的参数以插入或编辑记录时执行数据绑定。 也就是说,当用户单击 中的“编辑”按钮 ItemTemplate
时, Bind()
该方法将返回指定的数据字段值。 用户进行更改并单击“更新”后,与使用 Bind()
指定的数据字段对应的回发值将应用于 ObjectDataSource 的 UpdateParameters
。 或者,由 表示 <%# Eval("dataField") %>
的单向数据绑定仅在将数据绑定到模板时检索数据字段值, 并且不会 在回发时将用户输入的值返回到数据源的参数。
以下声明性标记显示了 FormView 的 EditItemTemplate
。 请注意, Bind()
方法在此处的数据绑定语法中使用,并且更新和取消按钮 Web 控件具有 CommandName
相应的属性设置。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ProductID: <asp:Label ID="ProductIDLabel1" runat="server" Text="<%# Eval("ProductID") %>"></asp:Label><br /> ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br /> UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName: <asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True" CommandName="Update" Text="Update"> </asp:LinkButton> <asp:LinkButton ID="UpdateCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </EditItemTemplate> <InsertItemTemplate> ... </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>
EditItemTemplate
此时,如果尝试使用它,将导致引发异常。 问题是 和 CategoryName
SupplierName
字段呈现为 中的 EditItemTemplate
TextBox Web 控件。 我们要么需要将这些 TextBox 更改为“标签”,要么完全删除它们。 让我们从 中完全 EditItemTemplate
删除它们。
图 23 显示了单击 Chai 的“编辑”按钮后浏览器中的 FormView。 请注意, SupplierName
中显示的 ItemTemplate
和 CategoryName
字段不再存在,因为我们刚刚从 EditItemTemplate
中删除了它们。 单击“更新”按钮时,FormView 将继续执行与 GridView 和 DetailsView 控件相同的步骤序列。
图 23:默认情况下,将 EditItemTemplate
每个可编辑的产品字段显示为 TextBox 或 CheckBox (单击以查看全尺寸图像)
单击“插入”按钮时,FormView ItemTemplate
会随之回发。 但是,不会将任何数据绑定到 FormView,因为正在添加新记录。 界面 InsertItemTemplate
包含用于添加新记录的 Web 控件以及“插入”和“取消”按钮。 Visual Studio 生成的默认值 InsertItemTemplate
包含每个非布尔值字段的 TextBox 和每个布尔值字段的 CheckBox,类似于自动生成 EditItemTemplate
的 接口。 TextBox 控件使用双向数据绑定将其 Text
属性绑定到相应数据字段的值。
以下声明性标记显示了 FormView 的 InsertItemTemplate
。 请注意, Bind()
方法在此处的数据绑定语法中使用,并且“插入”和“取消”按钮 Web 控件具有 CommandName
相应的属性设置。
<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"> <EditItemTemplate> ... </EditItemTemplate> <InsertItemTemplate> ProductName: <asp:TextBox ID="ProductNameTextBox" runat="server" Text="<%# Bind("ProductName") %>"> </asp:TextBox><br /> SupplierID: <asp:TextBox ID="SupplierIDTextBox" runat="server" Text="<%# Bind("SupplierID") %>"> </asp:TextBox><br /> CategoryID: <asp:TextBox ID="CategoryIDTextBox" runat="server" Text="<%# Bind("CategoryID") %>"> </asp:TextBox><br /> QuantityPerUnit: <asp:TextBox ID="QuantityPerUnitTextBox" runat="server" Text="<%# Bind("QuantityPerUnit") %>"> </asp:TextBox><br /> UnitPrice: <asp:TextBox ID="UnitPriceTextBox" runat="server" Text="<%# Bind("UnitPrice") %>"> </asp:TextBox><br /> UnitsInStock: <asp:TextBox ID="UnitsInStockTextBox" runat="server" Text="<%# Bind("UnitsInStock") %>"> </asp:TextBox><br /> UnitsOnOrder: <asp:TextBox ID="UnitsOnOrderTextBox" runat="server" Text="<%# Bind("UnitsOnOrder") %>"> </asp:TextBox><br /> ReorderLevel: <asp:TextBox ID="ReorderLevelTextBox" runat="server" Text="<%# Bind("ReorderLevel") %>"> </asp:TextBox><br /> Discontinued: <asp:CheckBox ID="DiscontinuedCheckBox" runat="server" Checked="<%# Bind("Discontinued") %>" /><br /> CategoryName: <asp:TextBox ID="CategoryNameTextBox" runat="server" Text="<%# Bind("CategoryName") %>"> </asp:TextBox><br /> SupplierName: <asp:TextBox ID="SupplierNameTextBox" runat="server" Text="<%# Bind("SupplierName") %>"> </asp:TextBox><br /> <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert" Text="Insert"> </asp:LinkButton> <asp:LinkButton ID="InsertCancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel"> </asp:LinkButton> </InsertItemTemplate> <ItemTemplate> ... </ItemTemplate> </asp:FormView>
FormView 的自动生成 有一个微妙之处 InsertItemTemplate
。 具体而言,即使为只读字段(如 CategoryName
和 SupplierName
)也会创建 TextBox Web 控件。 与 一样, EditItemTemplate
我们需要从 InsertItemTemplate
中删除这些 TextBox。
图 24 显示了添加新产品 Acme Coffee 时在浏览器中的 FormView。 请注意, SupplierName
中显示的 ItemTemplate
和 CategoryName
字段不再存在,因为我们刚刚删除了它们。 单击“插入”按钮时,FormView 将继续执行与 DetailsView 控件相同的步骤序列,向表添加新记录 Products
。 图 25 显示插入后,Acme Coffee 产品在 FormView 中的详细信息。
图 24:指示 InsertItemTemplate
FormView 的插入界面 (单击以查看全尺寸图像)
图 25:New Product, Acme Coffee 的详细信息显示在 FormView (单击以查看全尺寸图像)
通过将只读、编辑和插入接口分离到三个单独的模板中,FormView 可以比 DetailsView 和 GridView 更精细地控制这些接口。
注意
与 DetailsView 一样,FormView 的 CurrentMode
属性指示正在显示的接口,其 DefaultMode
属性指示完成编辑或插入后 FormView 返回到的模式。
总结
在本教程中,我们了解了使用 GridView、DetailsView 和 FormView 插入、编辑和删除数据的基础知识。 这三个控件都提供某种级别的内置数据修改功能,无需在 ASP.NET 页中编写一行代码即可使用,这要归功于数据 Web 控件和 ObjectDataSource。 但是,简单的点和单击技术呈现一个相当脆弱和天真的数据修改用户界面。 若要提供验证、注入编程值、正常处理异常、自定义用户界面等,我们需要依赖将在后续几个教程中讨论的一系列技术。
编程快乐!
关于作者
斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈