(VB) 插入、更新和删除数据的概述

作者 :Scott Mitchell

下载 PDF

本教程介绍如何将 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) 。

ObjectDataSource 的插入 () 、更新 () 和删除 () 方法充当 BLL 中的代理

图 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

为数据 Modification-Related 教程添加 ASP.NET 页面

图 2:为数据 Modification-Related 教程添加 ASP.NET 页

与其他文件夹中一样, Default.aspx 文件夹中 EditInsertDelete 会列出其部分中的教程。 回想一下, SectionLevelTutorialListing.ascx 用户控件提供了此功能。 因此,通过将用户控件从解决方案资源管理器拖动到Default.aspx页面的设计视图中,将此用户控件添加到 。

将 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 配置为使用此类。

将 ObjectDataSource 配置为使用 ProductsBLL 类

图 5:将 ObjectDataSource 配置为使用 ProductsBLL 类 (单击以查看全尺寸图像)

在下一个屏幕中,我们可以指定类的 ProductsBLL 哪些方法映射到 ObjectDataSource 的 Select()Insert()Update()Delete() ,方法是选择相应的选项卡并从下拉列表中选择方法。 图 6(现在看起来应该很熟悉)将 ObjectDataSource 的 Select() 方法映射到 ProductsBLL 类的 GetProducts() 方法。 Insert()可以通过从顶部列表中选择相应的选项卡来配置 、 Update()Delete() 方法。

让 ObjectDataSource 返回所有产品

图 6:让 ObjectDataSource 返回所有产品 (单击以查看全尺寸图像)

图 7、8 和 9 显示了 ObjectDataSource 的 UPDATE、INSERT 和 DELETE 选项卡。 配置这些选项卡, Insert()以便 、 Update()Delete() 方法分别调用 ProductsBLL 类的 UpdateProductAddProductDeleteProduct 方法。

将 ObjectDataSource 的 Update () 方法映射到 ProductBLL 类的 UpdateProduct 方法

图 7:将 ObjectDataSource 的 Update() 方法映射到 ProductBLL 类的方法 UpdateProduct (单击以查看全尺寸图像)

将 ObjectDataSource 的 Insert () 方法映射到 ProductBLL 类的 AddProduct 方法

图 8:将 ObjectDataSource 的 Insert() 方法映射到 ProductBLL 类的 Add Product 方法 (单击以查看全尺寸图像)

将 ObjectDataSource 的 Delete () 方法映射到 ProductBLL 类的 DeleteProduct 方法

图 9:将 ObjectDataSource 的 Delete() 方法映射到 ProductBLLDeleteProduct 的方法 (单击以查看全尺寸图像)

你可能已注意到,“更新”、“插入”和“删除”选项卡中的下拉列表已选中这些方法。 这要归功于我们使用 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> 指定要调用的基础对象和方法。 此外,还有 、 和 ,它们映射到类的 、 UpdateProductDeleteProduct 方法的AddProduct输入参数ProductsBLLInsertParametersUpdateParametersDeleteParameters

<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 会自动设置这些 DeleteParametersUpdateParametersUpdate()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 的属性是根据基础字段的元数据设置的。 例如, ProductIDCategoryNameSupplierName 字段在 中 ProductsDataTable 标记为只读,因此在编辑时不应更新。 为了适应这种情况,这些 BoundFields 的 ReadOnly 属性 设置为 True
  • DataKeyNames 属性分配给基础对象的主键字段 () 。 使用 GridView 编辑或删除数据时,这一点至关重要,因为此属性指示唯一标识每条记录的字段 (或字段集) 。 有关 属性的详细信息 DataKeyNames ,请参阅 使用带有 Details DetailView 的可选主网格视图的母版/详细信息 教程。

虽然 GridView 可以通过属性窗口或声明性语法绑定到 ObjectDataSource,但这样做需要手动添加适当的 BoundField 和DataKeyNames标记。

GridView 控件提供对行级编辑和删除的内置支持。 将 GridView 配置为支持删除会添加一列“删除”按钮。 当最终用户单击特定行的“删除”按钮时,将发生回发,GridView 将执行以下步骤:

  1. 分配 ObjectDataSource 的值 DeleteParameters ()
  2. 调用 ObjectDataSource 的 Delete() 方法,删除指定的记录
  3. 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 所示,通过浏览器访问此页面时,存在一列“删除”按钮。

CommandField 添加一列删除按钮

图 11:CommandField 添加删除按钮列 (单击以查看全尺寸图像)

如果你一直在自行构建本教程,在测试此页面时,单击“删除”按钮将引发异常。 继续阅读,了解引发这些异常的原因以及如何修复它们。

注意

如果你使用本教程随附的下载进行跟踪,则这些问题已经解决。 但是,我建议你通读下面列出的详细信息,以帮助确定可能出现的问题和合适的解决方法。

如果在尝试删除产品时收到异常,其消息类似于“ObjectDataSource 'ObjectDataSource1' 找不到具有参数的非泛型方法'DeleteProduct',original_ProductID”,则可能忘记从 ObjectDataSource 中删除属性 OldValuesParameterFormatStringOldValuesParameterFormatString指定 属性后,ObjectDataSource 尝试将 和 original_ProductID 输入参数DeleteProduct传入productID方法。 DeleteProduct但是,只接受单个输入参数,因此异常。 删除 OldValuesParameterFormatString 属性 (或将其设置为 {0}) 指示 ObjectDataSource 不尝试传入原始输入参数。

确保已清除 OldValuesParameterFormatString 属性

图 12:确保 OldValuesParameterFormatString 已清除属性 (单击以查看全尺寸图像)

即使删除OldValuesParameterFormatString了 属性,尝试删除产品时仍会收到异常,并显示以下消息:“DELETE 语句与 REFERENCE 约束'FK_Order_Details_Products'冲突。”Northwind 数据库包含 和 表之间的Order Details外键约束,这意味着,如果表中有一个或多个产品记录,则无法从系统中删除产品Order DetailsProducts 由于 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 将执行以下步骤:

  1. GridView 的 EditItemIndex 属性分配给单击了“编辑”按钮的行的索引
  2. GridView 通过调用其 Select() 方法将自身重新绑定到 ObjectDataSource
  3. 与 匹配的 EditItemIndex 行索引以“编辑模式”呈现。在此模式下,“编辑”按钮将替换为“更新”和“取消”按钮以及属性为 False 的 BoundFields ReadOnly , (默认) 呈现为 TextBox Web 控件,其 Text 属性分配给数据字段的值。

此时,标记将返回到浏览器,使最终用户能够对行的数据进行任何更改。 当用户单击“更新”按钮时,会发生回发,GridView 将执行以下步骤:

  1. ObjectDataSource 的值 UpdateParameters () 分配最终用户在 GridView 的编辑界面中输入的值
  2. 调用 ObjectDataSource 的 Update() 方法,更新指定的记录
  3. GridView 通过调用其 Select() 方法将自身重新绑定到 ObjectDataSource

在步骤 1 中分配给 UpdateParameters 的主键值来自 属性中指定的 DataKeyNames 值,而非主键值来自已编辑行的 TextBox Web 控件中的文本。 与删除一样,正确设置 GridView 的 DataKeyNames 属性至关重要。 如果缺少主键值, UpdateParameters 将在步骤 1 中为主键值分配值 Nothing ,这反过来又不会导致步骤 2 中的任何更新记录。

只需选中 GridView 的智能标记中的“启用编辑”复选框即可激活编辑功能。

选中“启用编辑”复选框

图 15:选中“启用编辑”复选框

选中“启用编辑”复选框将根据需要添加 CommandField () 并将其属性设置为 ShowEditButtonTrue。 如前所述,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。 这包括和 SupplierIDCategoryID字段,它们是其他表的键。

单击 Chai 的“编辑”按钮在编辑模式下显示行

图 16:单击“Chai”的“编辑”按钮在编辑模式下显示行 (单击以查看全尺寸图像)

除了要求用户直接编辑外键值外,编辑界面的界面还缺乏以下方面:

  • 如果用户输入 CategoryID 数据库中不存在的 或 SupplierIDUPDATE 将违反外键约束,从而导致引发异常。
  • 编辑界面不包含任何验证。 如果未提供所需的值 ((如 ProductName) ),或者输入应为数值的字符串值 (如在 UnitPrice 文本框中输入“太多!”) ,则会引发异常。 未来的教程将介绍如何将验证控件添加到编辑用户界面。
  • 目前, 所有 非只读产品字段都必须包含在 GridView 中。 如果我们要从 GridView 中删除字段,例如 UnitPrice,在更新数据时,GridView 不会设置 UnitPriceUpdateParameters 值,这会将数据库记录的 UnitPrice 更改为 NULL 值。 同样,如果从 GridView 中删除必填字段(如 ProductName),更新将失败,并出现上述相同的“列'ProductName'不允许 null”异常。
  • 编辑界面格式留下了很多需要。 UnitPrice显示有四个小数点的 。 理想情况下, CategoryIDSupplierID 值将包含列出系统中类别和供应商的 DropDownList。

这些都是我们目前必须面对的缺点,但将在将来的教程中解决。

使用 DetailsView 插入、编辑和删除数据

正如我们在前面的教程中看到的那样,DetailsView 控件一次显示一条记录,并且与 GridView 一样,允许编辑和删除当前显示的记录。 最终用户从 DetailsView 编辑和删除项目以及从 ASP.NET 端工作流的体验与 GridView 的体验相同。 DetailsView 与 GridView 的不同之处在于它还提供内置的插入支持。

若要演示 GridView 的数据修改功能,请首先将 DetailsView 添加到 Basics.aspx 现有 GridView 上方的页面,并通过 DetailsView 的智能标记将其绑定到现有 ObjectDataSource。 接下来,清除 DetailsView 的 HeightWidth 属性,并从智能标记中检查“启用分页”选项。 若要启用编辑、插入和删除支持,只需检查智能标记中的“启用编辑”、“启用插入”和“启用删除”复选框即可。

显示“详细信息”“查看任务”窗口的屏幕截图,其中选中了“启用插入”、“启用编辑”和“启用删除”复选框。

图 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 显示为一行,详细信息视图底部有“插入”、“编辑”和“删除”按钮。

DetailsView 的屏幕截图,其中 CommandField 显示为带有“插入”、“编辑”和“删除”按钮的底部行。

图 18:将 DetailsView 配置为支持编辑、插入和删除 (单击以查看全尺寸图像)

单击“删除”按钮启动与 GridView 相同的事件序列:回发;然后是 DetailsView,根据DataKeyNames值填充其 ObjectDataSource 的 DeleteParameters ;并完成调用其 ObjectDataSource 的 Delete() 方法,这实际上从数据库中删除了产品。 在 DetailsView 中编辑的工作方式与 GridView 相同。

对于插入,最终用户会看到一个“新建”按钮,单击该按钮时,在“插入模式”中呈现 DetailsView。使用“插入模式”时,“新建”按钮将替换为“插入”和“取消”按钮,并且仅显示属性设置为 True (默认) 的 BoundFieldInsertVisible。 标识为自动递增字段的数据字段(如 ProductID)在通过智能标记将 DetailsView 绑定到数据源时,其 InsertVisible 属性 设置为 False

通过智能标记将数据源绑定到 DetailsView 时,Visual Studio 仅为自动递增字段将 属性设置为 InsertVisibleFalse 。 只读字段(如 CategoryNameSupplierName)将在“插入模式”用户界面中显示,除非其 InsertVisible 属性显式设置为 False。 花点时间将这两个字段的属性 InsertVisible 设置为 False,无论是通过 DetailsView 的声明性语法,还是通过智能标记中的“编辑字段”链接。 图 19 显示通过单击“编辑字段”链接将False属性设置为 InsertVisible

显示“字段”窗口的屏幕截图,其中“InsertVisible”属性设置为 False。

图 19:Northwind Traders 现在提供 Acme 茶 (单击以查看全尺寸图像)

设置 InsertVisible 属性后,在浏览器中查看 Basics.aspx 页面,然后单击“新建”按钮。 图 20 显示了向我们的产品系列添加新饮料 Acme Tea 时的详细信息视图。

显示 Web 浏览器中Basics.aspx页的 DetailsView 的屏幕截图。

图 20:Northwind Traders 现在提供 Acme 茶 (单击以查看全尺寸图像)

输入 Acme Tea 的详细信息并单击“插入”按钮后,将进行回发,并将新记录添加到 Products 数据库表。 由于此 DetailsView 按在数据库表中存在的顺序列出产品,因此我们必须分页到最后一个产品才能查看新产品。

Acme 茶的详细信息

图 21:Acme Tea (详细信息 单击以查看全尺寸图像)

注意

DetailsView 的 CurrentMode 属性 指示正在显示的接口,可以是以下值之一: EditInsertReadOnlyDefaultMode 属性指示完成编辑或插入后 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 生成 EditItemTemplateInsertItemTemplate、 和 ItemTemplate ,用于收集用户的输入,并为“新建”、“编辑”、“删除”、“插入”、“更新”和“取消”按钮生成按钮 Web 控件。 此外,FormView 的 DataKeyNames 属性设置为 objectDataSource 返回的对象 (ProductID) 的主键字段。 最后,检查 FormView 的智能标记中的“启用分页”选项。

下面显示了 FormView 绑定到 ObjectDataSource 后 FormView 的 ItemTemplate 声明性标记。 默认情况下,每个非布尔值产品字段都绑定到 Text Label Web 控件的 属性,而 () Discontinued 的每个布尔值字段都绑定到 Checked 禁用的 CheckBox Web 控件的 属性。 为了使“新建”、“编辑”和“删除”按钮在单击时触发某些 FormView 行为,必须分别将其 CommandName 值设置为 NewEditDelete

<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 。 每个产品字段都列出,底部有“新建”、“编辑”和“删除”按钮。

Defaut FormView ItemTemplate Lists每个产品字段以及“新建”、“编辑”和“删除”按钮

图 22:Defaut FormView ItemTemplate Lists每个产品字段以及“新建”、“编辑”和“删除”按钮 (单击以查看全尺寸图像)

与 GridView 和 DetailsView 一样,单击“删除”按钮或任何属性设置为 Delete 的 CommandName Button、LinkButton 或 ImageButton 会导致回发,根据 FormView 的值填充 ObjectDataSource DeleteParametersDataKeyNames ,并调用 ObjectDataSource 的 Delete() 方法。

单击“编辑”按钮时,会随之回发,并且数据将反弹到 EditItemTemplate,后者负责呈现编辑界面。 此接口包括用于编辑数据的 Web 控件以及“更新”和“取消”按钮。 Visual Studio 生成的默认值 EditItemTemplate 包含用于) (任何自动递增字段 ProductID 的标签、每个非布尔值字段的 TextBox 和每个布尔值字段的 CheckBox。 此行为与 GridView 和 DetailsView 控件中自动生成的 BoundField 非常相似。

注意

FormView 自动生成 的 EditItemTemplate 一个小问题是,它为只读字段(如 CategoryNameSupplierName)呈现 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此时,如果尝试使用它,将导致引发异常。 问题是 和 CategoryNameSupplierName 字段呈现为 中的 EditItemTemplateTextBox Web 控件。 我们要么需要将这些 TextBox 更改为“标签”,要么完全删除它们。 让我们从 中完全 EditItemTemplate删除它们。

图 23 显示了单击 Chai 的“编辑”按钮后浏览器中的 FormView。 请注意, SupplierName 中显示的 ItemTemplateCategoryName 字段不再存在,因为我们刚刚从 EditItemTemplate中删除了它们。 单击“更新”按钮时,FormView 将继续执行与 GridView 和 DetailsView 控件相同的步骤序列。

默认情况下,EditItemTemplate 将每个可编辑的产品字段显示为 TextBox 或 CheckBox

图 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。 具体而言,即使为只读字段(如 CategoryNameSupplierName)也会创建 TextBox Web 控件。 与 一样, EditItemTemplate我们需要从 InsertItemTemplate中删除这些 TextBox。

图 24 显示了添加新产品 Acme Coffee 时在浏览器中的 FormView。 请注意, SupplierName 中显示的 ItemTemplateCategoryName 字段不再存在,因为我们刚刚删除了它们。 单击“插入”按钮时,FormView 将继续执行与 DetailsView 控件相同的步骤序列,向表添加新记录 Products 。 图 25 显示插入后,Acme Coffee 产品在 FormView 中的详细信息。

InsertItemTemplate 指示 FormView 的插入接口

图 24:指示 InsertItemTemplate FormView 的插入界面 (单击以查看全尺寸图像)

新产品 Acme Coffee 的详细信息显示在 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)。