插入、更新和删除数据概述 (C#)
作者 :斯科特·米切尔
本教程介绍如何将 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 数据库。
回想一下,在第一个教程中创建 TABLEAdapters 时,Visual Studio 会自动添加用于从基础数据库表插入、更新和删除数据的方法。 此外,在 “创建业务逻辑层 ”中,我们在 BLL 中设计了一种方法,这些方法可向下调用这些数据修改 DAL 方法。
除了其Select()
方法外,ObjectDataSource 还具有Insert()
和Update()
Delete()
方法。 与该方法 Select()
一样,这三种方法可以映射到基础对象中的方法。 配置为插入、更新或删除数据时,GridView、DetailsView 和 FormView 控件提供用于修改基础数据的用户界面。 此用户界面调用 Insert()
ObjectDataSource 的和Delete()
Update()
方法,然后调用基础对象的关联方法(请参阅图 1)。
图 1:ObjectDataSource 的Insert()
和Update()
Delete()
方法充当 BLL 中的代理(单击以查看全尺寸图像)
本教程介绍如何将 ObjectDataSource 和Insert()
Update()
Delete()
方法映射到 BLL 中的类的方法,以及如何配置 GridView、DetailsView 和 FormView 控件以提供数据修改功能。
步骤 1:创建插入、更新和删除教程网页
在开始探索如何插入、更新和删除数据之前,让我们先花点时间在网站项目中创建 ASP.NET 页面,本教程和接下来需要几个页面。 首先添加名为 <Site.master
母版页相关联:
Default.aspx
Basics.aspx
DataModificationEvents.aspx
ErrorHandling.aspx
UIValidation.aspx
CustomizedUI.aspx
OptimisticConcurrency.aspx
ConfirmationOnDelete.aspx
UserLevelAccess.aspx
图 2:为与数据修改相关的教程添加 ASP.NET 页
与其他文件夹中一样, Default.aspx
该 EditInsertDelete
文件夹中将列出其部分中的教程。 回想一下, SectionLevelTutorialListing.ascx
用户控件提供了此功能。 因此,通过将此用户控件从解决方案资源管理器拖动到Default.aspx
页面的设计视图中,将其添加到该控件。
图 3:将用户控件Default.aspx
添加到 SectionLevelTutorialListing.ascx
(单击以查看全尺寸图像)
最后,将页面添加为文件的条目 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 拖到设计器上,然后单击其智能标记中的“配置数据源”链接。 由于唯一提供编辑、插入和删除方法的 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()
分别调用ProductsBLL
类的UpdateProduct
、Update()
AddProduct
方法和Delete()
DeleteProduct
方法。
图 7:将 ObjectDataSource Update()
的方法映射到 ProductBLL
类 UpdateProduct
的方法(单击以查看全尺寸图像)
图 8:将 ObjectDataSource Insert()
的方法映射到 ProductBLL
类的 Add Product
方法(单击以查看全尺寸图像)
图 9:将 ObjectDataSource Delete()
的方法映射到 ProductBLL
类 DeleteProduct
的方法(单击以查看全尺寸图像)
你可能已注意到 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表已选择这些方法。 这要归功于我们使用 DataObjectMethodAttribute
修饰方法的方法 ProductsBLL
。 例如,DeleteProduct 方法具有以下签名:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteProduct(int productID)
{
...
}
该 DataObjectMethodAttribute
属性指示每个方法的用途,无论是用于选择、插入、更新或删除,还是它是默认值。 如果在创建 BLL 类时省略了这些属性,则需要从 UPDATE、INSERT 和 DELETE 选项卡中手动选择这些方法。
确保将适当的ProductsBLL
方法映射到 ObjectDataSource,Insert()
Update()
以及Delete()
方法后,单击“完成”以完成向导。
检查 ObjectDataSource 的标记
通过其向导配置 ObjectDataSource 后,转到“源”视图,检查生成的声明性标记。 该 <asp:ObjectDataSource>
标记指定要调用的基础对象和方法。 此外,还有DeleteParameters
UpdateParameters
,并且InsertParameters
映射到类的UpdateProduct
AddProduct
输入参数ProductsBLL
和DeleteProduct
方法:
<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 包含其关联方法的每个输入参数的参数,就像 SelectParameter
将 ObjectDataSource 配置为调用需要输入参数(如 GetProductsByCategoryID(categoryID)
)的选择方法时存在一样。 正如我们不久将看到这些DeleteParameters
UpdateParameters
值,并且InsertParameters
是在调用 ObjectDataSource 的 Insert()
、Update()
或Delete()
方法之前由 GridView、DetailsView 和 FormView 自动设置的。 还可以根据需要以编程方式设置这些值,我们将在将来的教程中讨论。
使用向导配置 ObjectDataSource 的一个副作用是,Visual Studio 将 OldValuesParameterFormatString 属性 设置为 original_{0}
。 此属性值用于包括正在编辑的数据的原始值,在两种情况下非常有用:
- 如果在编辑记录时,用户能够更改主键值。 在这种情况下,必须提供新的主键值和原始主键值,以便找到具有原始主键值的记录并相应地更新其值。
- 使用乐观并发时。 乐观并发是一种技术,可确保两个同时用户不会覆盖彼此的更改,并且是将来教程的主题。
该 OldValuesParameterFormatString
属性指示基础对象的更新和删除原始值的输入参数的名称。 在探索乐观并发时,我们将更详细地讨论此属性及其用途。 但是,现在我提出它,因为我们的 BLL 方法不需要原始值,因此,请务必删除此属性。 OldValuesParameterFormatString
当数据 Web 控件尝试调用 ObjectDataSource 或Delete()
Update()
方法时,将属性设置为默认值{0}
以外的任何内容都会导致错误,因为 ObjectDataSource 将尝试同时传入UpdateParameters
或DeleteParameters
指定值参数或原始值参数。
如果目前还不清楚这一点,请不要担心,我们将在将来的教程中检查此属性及其实用工具。 目前,只需确定完全从声明性语法中删除此属性声明或将值设置为默认值({0})。
注意
如果只是从设计视图中的属性窗口中清除OldValuesParameterFormatString
属性值,该属性仍将存在于声明性语法中,但设置为空字符串。 不幸的是,这仍然会导致上面讨论的相同问题。 因此,从声明性语法中删除该属性,或者从属性窗口中将值设置为默认值{0}
。
步骤 3:添加数据 Web 控件并将其配置为进行数据修改
将 ObjectDataSource 添加到页面并配置后,即可向页面添加数据 Web 控件以显示数据,并为最终用户提供修改数据的方法。 我们将单独查看 GridView、DetailsView 和 FormView,因为这些数据 Web 控件在数据修改功能和配置方面有所不同。
如本文的其余部分所述,通过 GridView、DetailsView 和 FormView 控件添加非常基本的编辑、插入和删除支持,这与选中几个复选框一样简单。 现实世界中有许多微妙之处和边缘情况,使得提供此类功能比只需点和单击即可。 但是,本教程只侧重于证明简单数据修改功能。 未来的教程将探讨在现实世界中无疑会出现的担忧。
从 GridView 中删除数据
首先,将 GridView 从工具箱拖到设计器上。 接下来,通过从 GridView 智能标记中的下拉列表中选择 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 属性 分配给基础对象的主键字段(s)。 当使用 GridView 编辑或删除数据时,这一点至关重要,因为此属性指示唯一标识每个记录的字段(或字段集)。 有关该属性的详细信息
DataKeyNames
,请参阅 “使用可选择的主网格视图和详细信息详细信息”详细信息视图 “教程。
虽然 GridView 可以通过属性窗口或声明性语法绑定到 ObjectDataSource,但这样做需要手动添加相应的 BoundField 和DataKeyNames
标记。
GridView 控件提供对行级编辑和删除的内置支持。 配置 GridView 以支持删除添加一列“删除”按钮。 当最终用户单击特定行的“删除”按钮时,随后会出现回发,GridView 将执行以下步骤:
- 分配 ObjectDataSource
DeleteParameters
的值 - 调用 ObjectDataSource
Delete()
的方法,删除指定的记录 - GridView 通过调用其
Select()
方法将自身重新绑定到 ObjectDataSource
分配给 DeleteParameters
这些值是单击“删除”按钮的行的 DataKeyNames
字段值。 因此,必须正确设置 GridView DataKeyNames
的属性。 如果缺少, DeleteParameters
则会在步骤 1 中分配一个 null
值,这反过来又不会导致步骤 2 中的任何已删除记录。
注意
集合 DataKeys
存储在 GridView 的控制状态中,这意味着 DataKeys
即使 GridView 视图视图状态已禁用,也会在回发中记住这些值。 但是,对于支持编辑或删除(默认行为)的 GridView,仍启用视图状态非常重要。 如果将 GridView 属性 EnableViewState
设置为 false
,则编辑和删除行为将适用于单个用户,但如果存在并发用户删除数据,则这些并发用户可能会意外删除或编辑他们不打算的记录。
此相同的警告也适用于 DetailsViews 和 FormViews。
若要将删除功能添加到 GridView,只需转到其智能标记并选中“启用删除”复选框。
图 10:选中“启用删除”复选框
选中智能标记中的“启用删除”复选框会将 CommandField 添加到 GridView。 CommandField 在 GridView 中呈现一列,其中包含用于执行以下一个或多个任务的按钮:选择记录、编辑记录和删除记录。 我们之前已看到 CommandField 在 Master/Detail 中使用可选择的主网格视图和 Details DetailView 教程中的记录进行操作。
CommandField 包含许多 ShowXButton
属性,这些属性指示 CommandField 中显示的按钮系列。 通过选中“启用删除”复选框,命令字段的属性ShowDeleteButton
true
已添加到 GridView 的 Columns 集合中。
<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”:productID,original_ProductID“,你可能会忘记从 ObjectDataSource 中删除 OldValuesParameterFormatString
该属性。 指定属性后OldValuesParameterFormatString
,ObjectDataSource 将尝试传入和productID
original_ProductID
输入参数传递给DeleteProduct
该方法。 DeleteProduct
但是,仅接受单个输入参数,因此异常。 删除 OldValuesParameterFormatString
属性(或将其设置为 {0}
)指示 ObjectDataSource 不尝试传入原始输入参数。
图 12:确保 OldValuesParameterFormatString
已清除属性(单击以查看全尺寸图像)
即使删除了该OldValuesParameterFormatString
属性,在尝试删除包含消息的产品时,仍会收到异常:“DELETE 语句与 REFERENCE 约束”FK_Order_Details_Products“冲突。Northwind 数据库包含表之间的Order Details
Products
外键约束,这意味着如果表中存在一个或多个记录Order Details
,则无法从系统中删除产品。 由于 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
行索引以“编辑模式”呈现。在此模式下,“编辑”按钮由“更新”和“取消”按钮和 BoundFields 替换,其ReadOnly
属性为 False(默认值)呈现为 TextBox Web 控件,其Text
属性被分配给数据字段的值。
此时,标记将返回到浏览器,使最终用户能够对行的数据进行任何更改。 当用户单击“更新”按钮时,将发生回发,GridView 将执行以下步骤:
- 将 ObjectDataSource 的值
UpdateParameters
分配给最终用户在 GridView 的编辑界面中输入的值 - 调用 ObjectDataSource
Update()
的方法,更新指定的记录 - GridView 通过调用其
Select()
方法将自身重新绑定到 ObjectDataSource
分配给 UpdateParameters
步骤 1 中的主键值来自属性中指定的 DataKeyNames
值,而非主键值来自编辑行的 TextBox Web 控件中的文本。 与删除一样,必须正确设置 GridView DataKeyNames
的属性。 如果缺少, UpdateParameters
主键值将在步骤 1 中分配一个 null
值,这反过来又不会导致步骤 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。 这包括类似于 CategoryID
其他表的字段, SupplierID
这些字段是其他表的键。
图 16:单击 Chai 的“编辑”按钮在编辑模式下显示行(单击以查看全尺寸图像)
除了要求用户直接编辑外键值外,编辑界面的界面还缺少以下方法:
- 如果用户输入数据库中不存在的
CategoryID
或SupplierID
不存在的,UPDATE
将违反外键约束,导致引发异常。 - 编辑界面不包含任何验证。 如果未提供所需的值(例如
ProductName
),或输入预期数值的字符串值(例如在UnitPrice
文本框中输入“太多!” ),将引发异常。 未来的教程将介绍如何将验证控件添加到编辑用户界面。 - 目前, 不是只读的所有 产品字段都必须包含在 GridView 中。 如果我们要从 GridView 中删除字段,例如
UnitPrice
,在更新数据时,GridView 不会设置UpdateParameters
UnitPrice
该值,这会将数据库记录UnitPrice
更改为值NULL
。 同样,如果从 GridView 中删除所需的字段,ProductName
更新将失败,并出现上述相同“Column 'ProductName' 不允许 nulls”异常。 - 编辑界面格式设置需要很多。 显示
UnitPrice
四个小数点。 理想情况下,CategoryID
和SupplierID
值将包含 DropDownLists,用于列出系统中的类别和供应商。
这些都是我们现在必须忍受的缺点,但在将来的教程中将得到解决。
使用 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 显示为一行,在 DetailsView 底部带有“插入”、“编辑”和“删除”按钮。
图 18:配置 DetailsView 以支持编辑、插入和删除(单击以查看全尺寸图像)
单击“删除”按钮启动与 GridView 相同的事件序列:回发;后跟基于值的 DetailsView 填充其 ObjectDataSource;并使用调用 ObjectDataSource DeleteParameters
DataKeyNames
Delete()
的方法完成,该方法实际上从数据库中删除了产品。 在 DetailsView 中编辑的工作方式也与 GridView 相同。
对于插入,最终用户会显示一个“新建”按钮,单击该按钮时,将在“插入模式”中呈现 DetailsView。使用“插入模式”时,“新建”按钮将替换为“插入”和“取消”按钮,并且仅显示属性设置为true
“默认值”的 InsertVisible
BoundFields。 标识为自动递增字段的数据字段(例如ProductID
,在通过智能标记将 DetailsView 绑定到数据源时,其 InsertVisible 属性设置为false
该属性)。
通过智能标记将数据源绑定到 DetailsView 时,Visual Studio 将属性设置为InsertVisible
false
仅针对自动递增字段。 只读字段(如 CategoryName
和 SupplierName
)将显示在“插入模式”用户界面中,除非其 InsertVisible
属性显式设置为 false
< a0/>。 花点时间将这两个字段 InsertVisible
的属性 false
设置为:通过 DetailsView 的声明性语法或通过智能标记中的“编辑字段”链接。 图 19 显示通过单击“编辑字段”链接将属性设置为InsertVisible
false
”
图 19:Northwind Traders Now Offers Acme Tea (单击查看全尺寸图像)
设置 InsertVisible
属性后,在浏览器中查看 Basics.aspx
页面,然后单击“新建”按钮。 图 20 显示了在向我们的生产线添加新饮料 Acme Tea 时 DetailsView。
图 20:Northwind Traders Now Offers Acme Tea (单击查看全尺寸图像)
输入 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 控件必须手动添加到相应的模板中。 幸运的是,Visual Studio 在通过智能标记中的下拉列表将 FormView 绑定到数据源时,将自动创建所需的接口。
为了说明这些技术,请先将 FormView 添加到 Basics.aspx
页面,然后从 FormView 的智能标记将其绑定到已创建的 ObjectDataSource。 这将为 FormView 生成一个 EditItemTemplate
, InsertItemTemplate
并为 ItemTemplate
包含 TextBox Web 控件的 FormView 生成,用于收集新、编辑、删除、插入、更新和取消按钮的用户输入和按钮 Web 控件。 此外,FormView 的属性 DataKeyNames
设置为 ObjectDataSource 返回的对象的主键字段(ProductID
)。 最后,检查 FormView 智能标记中的“启用分页”选项。
下面显示了 FormView 绑定到 ObjectDataSource 后 FormView 的 ItemTemplate
声明性标记。 默认情况下,每个非布尔值产品字段都绑定到 Text
标签 Web 控件的属性,而每个布尔值字段 (Discontinued
) 都绑定到 Checked
禁用的 CheckBox Web 控件的属性。 为了使“新建”、“编辑”和“删除”按钮在单击时触发某些 FormView 行为,必须CommandName
分别将其值设置为New
<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
列出了每个产品字段以及“新建”、“编辑”和“删除”按钮(单击可查看全尺寸图像)
与 GridView 和 DetailsView 一样,单击“删除”按钮或任何按钮、LinkButton 或 ImageButton 的属性CommandName
设置为 Delete 会导致回发,根据 FormView DataKeyNames
的值填充 ObjectDataSource,并调用 ObjectDataSource DeleteParameters
Delete()
的方法。
单击“编辑”按钮后,会回发,数据会反弹到 EditItemTemplate
该按钮,该按钮负责呈现编辑界面。 此接口包括用于编辑数据的 Web 控件以及“更新”和“取消”按钮。 Visual Studio 生成的默认值 EditItemTemplate
包含任何自动递增字段的标签(ProductID
)、每个非布尔值字段的 TextBox,以及每个布尔值字段的 CheckBox。 此行为与 GridView 和 DetailsView 控件中自动生成的 BoundFields 非常相似。
注意
FormView 自动生成 EditItemTemplate
的一个小问题是,它为只读字段呈现 TextBox Web 控件,例如 CategoryName
和 SupplierName
。 我们将很快了解如何考虑这一点。
TextBox 控件EditItemTemplate
的属性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
字段呈现为 TextBox Web 控件。EditItemTemplate
我们要么需要将这些 TextBox 更改为标签,要么将它们全部删除。 让我们从中 EditItemTemplate
完全删除它们。
图 23 显示了在单击 Chai 的“编辑”按钮后浏览器中的 FormView。 请注意,SupplierName
中显示的字段CategoryName
ItemTemplate
不再存在,因为我们只是从中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
中显示的字段CategoryName
ItemTemplate
不再存在,因为我们只是删除了它们。 单击“插入”按钮时,FormView 将继续执行与 DetailsView 控件相同的步骤序列,向表添加新记录 Products
。 图 25 显示在 FormView 中插入 Acme Coffee 产品的详细信息。
图 24: InsertItemTemplate
指示 FormView 的插入界面(单击以查看全尺寸图像)
图 25:新产品(Acme Coffee)的详细信息显示在 FormView 中(单击以查看全尺寸图像)
通过将只读、编辑和插入接口分离到三个单独的模板中,FormView 允许对这些接口进行比 DetailsView 和 GridView 更精细的控制。
注意
与 DetailsView 一样,FormView CurrentMode
的属性指示显示接口,其 DefaultMode
属性指示 FormView 在编辑或插入完成后返回的模式。
总结
本教程介绍了使用 GridView、DetailsView 和 FormView 插入、编辑和删除数据的基础知识。 这三个控件都提供了一些级别的内置数据修改功能,由于数据 Web 控件和 ObjectDataSource,无需在 ASP.NET 页中编写单行代码即可利用这些功能。 但是,简单点和单击技术呈现一个相当虚弱和天真的数据修改用户界面。 若要提供验证、注入编程值、妥善处理异常、自定义用户界面等,我们需要依赖将在接下来的几个教程中讨论的技术。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET。