作者 :斯科特·米切尔
了解如何在单个作中插入多个数据库记录。 在用户界面层中,我们扩展 GridView 以允许用户输入多个新记录。 在数据访问层中,为确保所有插入操作成功或全部回滚,我们将多个插入操作封装在一个事务中。
介绍
在 Batch 更新 教程中,我们介绍了如何自定义 GridView 控件,以呈现可编辑多个记录的接口。 访问页面的用户可以进行一系列更改,然后单击一下按钮,执行批处理更新。 对于用户通常一次性更新许多记录的情况,与在 “插入、更新和删除数据概述”教程 中首次探索的默认每行编辑功能相比,此类界面可以减少无数次的点击和从键盘切换到鼠标的上下文转换。
添加记录时也可以应用此概念。 假设在 Northwind Traders,我们经常收到来自供应商的货物,这些货物中包含某个特定类别的大量产品。 例如,我们可能会从东京商人那里收到六种不同的茶和咖啡产品。 如果用户通过 DetailsView 控件一次输入六个产品,则必须反复选择许多相同的值:他们需要选择同一类别(饮料)、同一供应商(东京贸易商)、相同的停产值(False),以及订单值(0)上的相同单位。 此重复数据输入不仅耗时,而且容易出错。
只需稍作工作,即可创建一个批处理插入界面,使用户能够选择供应商和类别一次,输入一系列产品名称和单价,然后单击一个按钮将新产品添加到数据库(请参阅图 1)。 添加每个产品时,会将其ProductName
和UnitPrice
数据字段分配为 TextBoxes 中输入的值,并且将其CategoryID
和SupplierID
的值分配为窗体顶部 DropDownLists 中的值。
Discontinued
和UnitsOnOrder
的值分别被设置为硬编码值False
和0。
图 1:批处理插入界面(单击以查看全尺寸图像)
在本教程中,我们将创建一个页面,用于实现图 1 中显示的批处理插入接口。 与前面的两个教程一样,我们将在事务范围内包装插入以确保原子性。 让我们开始吧!
步骤 1:创建显示界面
本教程由一个页面组成,分为两个区域:显示区域和插入区域。 我们将在此步骤中创建的显示界面显示 GridView 中的产品,并包含标题为“流程产品发货”的按钮。 单击此按钮时,显示界面将替换为插入接口,如图 1 所示。 单击“从发货添加产品”或“取消”按钮后,显示界面将返回。 我们将在步骤 2 中创建插入接口。
创建具有两个接口的页面时,一次只显示其中一个接口,每个接口通常放置在 面板 Web 控件中,该控件充当其他控件的容器。 因此,我们的页面将为每个接口提供两个面板控件。
首先打开BatchInsert.aspx
页面所在的BatchData
文件夹,然后将面板从工具箱拖动到设计器上(参见图 2)。 将 Panel 属性 ID
设置为 DisplayInterface
. 将面板添加到设计器时,其 Height
属性 Width
分别设置为 50px 和 125px。 从“属性”窗口中清除这些属性值。
图 2:将面板从工具箱拖到设计器上(单击以查看全尺寸图像)
接下来,将 Button 和 GridView 控件拖到面板中。 将按钮的 ID
属性设置为 ProcessShipment
,并将其 Text
属性设置为“处理产品发货”。 将 GridView 属性ID
ProductsGrid
设置为其智能标记,并将其绑定到名为 ProductsDataSource
的新 ObjectDataSource。 将 ObjectDataSource 配置为从 ProductsBLL
类 s GetProducts
方法拉取其数据。 由于此 GridView 仅用于显示数据,因此请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为“无”。 单击“完成”以完成“配置数据源”向导。
图 3:显示从 ProductsBLL
类方法 GetProducts
返回的数据(单击以查看全尺寸图像)
图 4:将 UPDATE、INSERT 和 DELETE 选项卡中的“Drop-Down 列表”设置为“无”(单击可查看全尺寸图像)
完成 ObjectDataSource 向导后,Visual Studio 将为产品数据字段添加 BoundFields 和 CheckBoxField。 保留ProductName
、CategoryName
、SupplierName
、UnitPrice
和Discontinued
字段,删除其他所有字段。 随意进行任何审美自定义。 我决定将 UnitPrice
字段的格式设置为货币值,对字段重新排序,并重命名了几个字段 HeaderText
值。 此外,通过选中 GridView 智能标记中的“启用分页”和“启用排序”复选框,将 GridView 配置为包含分页和排序支持。
添加面板、Button、GridView 和 ObjectDataSource 控件并自定义 GridView 字段后,页面的声明性标记应如下所示:
<asp:Panel ID="DisplayInterface" runat="server">
<p>
<asp:Button ID="ProcessShipment" runat="server"
Text="Process Product Shipment" />
</p>
<asp:GridView ID="ProductsGrid" runat="server" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
ReadOnly="True" SortExpression="SupplierName" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False"
SortExpression="UnitPrice">
<ItemStyle HorizontalAlign="Right" />
</asp:BoundField>
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued">
<ItemStyle HorizontalAlign="Center" />
</asp:CheckBoxField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>
</asp:Panel>
请注意,Button 和 GridView 的标记出现在开始和结束的 <asp:Panel>
标签内。 由于这些控件位于面板内 DisplayInterface
,因此只需将 Panel 的属性 Visible
设置为 False
,即可隐藏它们。 步骤 3 以编程方式更改 Panel Visible
属性,以响应按钮单击以显示一个界面,同时隐藏另一个界面。
花点时间通过浏览器查看进度。 如图 5 所示,应会看到 GridView 上方的“流程产品发货”按钮,该按钮一次列出十个产品。
图 5:GridView 列出了提供排序和分页功能的产品列表(单击可查看全尺寸图像)
步骤 2:创建插入接口
显示界面完成后,我们便可以创建插入接口。 在本教程中,让我们创建一个插入界面,提示输入单个供应商和类别值,然后允许用户输入最多五个产品名称和单价值。 使用此界面,用户可以添加一到五种新产品,这些新产品共享相同的类别和供应商,但具有独特的产品名称和价格。
首先,将面板从工具箱拖到设计器上,将其置于现有 DisplayInterface
面板下方。 将新添加的 Panel 的 ID
属性设置为 InsertingInterface
,并将其 Visible
属性设置为 False
。 我们将在步骤 3 中添加代码,以设置 InsertingInterface
Panel 的 Visible
属性为 True
。 此外,请清除面板的Height
和Width
属性值。
接下来,我们需要创建如图 1 所示的插入接口。 可以通过各种 HTML 技术创建此接口,但我们将使用一个非常简单的接口:四列七行表。
注释
输入 HTML <table>
元素的标记时,我更喜欢使用源视图。 虽然 Visual Studio 确实具有通过设计器添加 <table>
元素的工具,但设计器似乎太乐意将未请求的 style
设置注入到标记中。 创建 <table>
标记后,通常返回到设计器以添加 Web 控件并设置其属性。 使用预先确定的列和行创建表时,我更喜欢使用静态 HTML 而不是 表 Web 控件 ,因为放置在表 Web 控件中的任何 Web 控件只能使用 FindControl("controlID")
模式进行访问。 但是,我确实对动态大小的表(行或列基于某些数据库或用户指定的条件)使用表 Web 控件,因为可以通过编程方式构造表 Web 控件。
在 <asp:Panel>
面板的 InsertingInterface
标记中输入以下标记:
<table class="DataWebControlStyle" cellspacing="0">
<tr class="BatchInsertHeaderRow">
<td class="BatchInsertLabel">Supplier:</td>
<td></td>
<td class="BatchInsertLabel">Category:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertAlternatingRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertAlternatingRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertFooterRow">
<td colspan="4">
</td>
</tr>
</table>
此 <table>
标记尚不包含任何 Web 控件,我们将立即添加这些控件。 请注意,每个 <tr>
元素包含特定的 CSS 类设置:BatchInsertHeaderRow
是用于供应商和类别下拉列表将被放置的标题行,BatchInsertFooterRow
是用于“添加发货产品”和“取消”按钮的页脚行,BatchInsertRow
和 BatchInsertAlternatingRow
则交替用于包含产品和单价文本框控件的各行。 我在文件中创建了相应的 CSS 类 Styles.css
,使插入接口的外观类似于我们在整个教程中使用的 GridView 和 DetailsView 控件。 下面显示了这些 CSS 类。
/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/
.BatchInsertLabel
{
font-weight: bold;
text-align: right;
}
.BatchInsertHeaderRow td
{
color: White;
background-color: #900;
padding: 11px;
}
.BatchInsertFooterRow td
{
text-align: center;
padding-top: 5px;
}
.BatchInsertRow
{
}
.BatchInsertAlternatingRow
{
background-color: #fcc;
}
输入此标记后,返回到“设计”视图。 如图 6 所示,这 <table>
应显示为设计器中的四列七行表。
图 6:插入接口由四列组成,Seven-Row 表(单击以查看全尺寸图像)
我们现在已准备好将 Web 控件添加到插入界面。 将两个下拉列表从工具箱拖到表中的指定单元格内,一个用于供应商,一个用于类别。
设置供应商 DropDownList 的 ID
属性为 Suppliers
,并绑定到名为 SuppliersDataSource
的新 ObjectDataSource。 配置新的 ObjectDataSource 以从 SuppliersBLL
类中的 GetSuppliers
方法检索数据,并将 UPDATE 选项卡的下拉列表框设置为 “None”。 单击“完成”以完成向导。
图 7:将 ObjectDataSource 配置为使用 SuppliersBLL
类方法 GetSuppliers
(单击以查看全尺寸图像)
让 Suppliers
DropDownList 显示 CompanyName
数据字段,并使用 SupplierID
数据字段作为其 ListItem
值。
图 8:显示 CompanyName
数据字段并用作 SupplierID
值(单击以查看全尺寸图像)
将第二个 DropDownList Categories
命名并绑定到名为 CategoriesDataSource
的新 ObjectDataSource。 将 CategoriesDataSource
ObjectDataSource 配置为使用 CategoriesBLL
类 s GetCategories
方法;将 UPDATE 和 DELETE 选项卡中的下拉列表设置为 “无”,然后单击“完成”以完成向导。 最后,让 DropDownList 显示 CategoryName
数据字段,并使用 CategoryID
该值作为值。
添加这两个 DropDownList 并将其绑定到适当配置的 ObjectDataSources 后,屏幕应类似于图 9。
图 9:标题行现在包含 Suppliers
和 Categories
DropDownList(单击以查看全尺寸图像)
现在,我们需要创建 TextBoxes 来收集每个新产品的名称和价格。 将一个 TextBox 控件从工具箱拖动到设计器中,用于五个产品名称和价格行中的每一行。 将 ID
TextBoxes 的属性设置为 ProductName1
、 UnitPrice1
、 ProductName2
、 UnitPrice2
、 ProductName3
、 UnitPrice3
等。
在每个单价 TextBox 之后添加 CompareValidator,将 ControlToValidate
属性设置为相应的 ID
属性。 此外,将Operator
属性设置为GreaterThanEqual
,将ValueToCompare
设置为0,并将Type
设置为Currency
。 这些设置指示 CompareValidator 确保输入的价格是大于或等于零的有效货币值。 将 Text
属性设置为 *,并且 ErrorMessage
价格必须大于或等于零。 此外,请省略任何货币符号。
注释
插入接口不包含任何 RequiredFieldValidator 控件,即使 ProductName
数据库表中的 Products
字段不允许 NULL
值。 这是因为我们希望允许用户输入最多五个产品。 例如,如果用户要为前三行提供产品名称和单价,将最后两行留空,则我们只向系统添加三个新产品。 但是,由于ProductName
是必需的,因此我们需要以编程方式检查,以确保如果输入了单价,则必须提供相应的产品名称值。 我们将在步骤 4 中处理此检查。
验证用户输入时,如果值包含货币符号,CompareValidator 将报告无效数据。 在每个单价 TextBox 的前面添加一个 $ 作为视觉提示,指示用户在输入价格时省略货币符号。
最后,在InsertingInterface
面板中添加 ValidationSummary 控件,将其ShowMessageBox
属性设置为True
,并将其ShowSummary
属性设置为False
。 使用这些设置时,如果用户输入了无效的单价值,则会在有问题的 TextBox 控件旁边显示星号,ValidationSummary 将显示一个客户端消息框,其中显示了前面指定的错误消息。
此时,屏幕应类似于图 10。
图 10:插入界面现在包括产品名称和价格的文本框(单击以查看全尺寸图像)
接下来,我们需要将“从发货添加产品”和“取消”按钮添加到页脚行。 将工具箱中的两个按钮控件拖到插入界面的页脚中,分别将 Buttons ID
属性AddProducts
和CancelButton
Text
属性设置为“从发货和取消添加产品”。 此外,将 CancelButton
控件 CausesValidation
的属性设置为 false
.
最后,我们需要添加一个标签 Web 控件,用于显示两个接口的状态消息。 例如,当用户成功添加新的产品发货时,我们希望返回到显示界面并显示确认消息。 但是,如果用户为新产品提供价格,但会离开产品名称,我们需要显示一条警告消息,因为 ProductName
该字段是必需的。 由于我们需要这两个接口显示此消息,因此将其放置在面板外部页面顶部。
将标签 Web 控件从工具箱拖到设计器中的页面顶部。 将ID
属性设置为StatusLabel
,清除Text
属性,并将属性EnableViewState
设置为 Visible
False
。 正如我们在前面的教程中看到的那样,设置EnableViewState
属性为False
允许我们通过程序更改Label的属性值,并在后续回发时自动恢复为默认值。 这简化了代码,用于在某些用户动作后显示状态消息,而这些消息会在随后的回发中消失。 最后,将 StatusLabel
控件的属性 CssClass
设置为 Warning,这是一个 CSS 类的名称,该类以 Styles.css
大、斜体、粗体、红色字体显示文本。
图 11 显示了在添加和配置 Label 控件后的 Visual Studio 设计器。
图 11:将 StatusLabel
控件置于两个面板控件上方(单击以查看全尺寸图像)
步骤 3:在显示接口和插入接口之间切换
此时,我们已完成显示和插入接口的标记,但仍剩下两项任务:
- 在显示接口和插入接口之间切换
- 将发货中的产品添加到数据库
目前,显示界面可见,但插入接口处于隐藏状态。 这是因为 DisplayInterface
Panel 属性 Visible
设置为 True
(默认值),而 InsertingInterface
Panel Visible
属性设置为 False
。 若要在两个接口之间切换,只需切换每个控件的 Visible
属性值。
当单击“流程产品发货”按钮时,我们希望从显示界面移动到插入界面。 因此,为此 Button 事件创建包含以下代码的 Click
事件处理程序:
Protected Sub ProcessShipment_Click(sender As Object, e As EventArgs) _
Handles ProcessShipment.Click
DisplayInterface.Visible = False
InsertingInterface.Visible = True
End Sub
此代码只是隐藏 DisplayInterface
面板并显示 InsertingInterface
面板。
接下来,在插入界面中为“从发货添加产品”和“取消按钮”控件创建事件处理程序。 单击其中任一按钮时,需要还原回显示界面。 为两个 Button 控件创建 Click
事件处理程序,以便调用 ReturnToDisplayInterface
,我们将很快添加此方法。 除了隐藏 InsertingInterface
面板和显示 DisplayInterface
面板之外, ReturnToDisplayInterface
该方法还需要将 Web 控件返回到其预编辑状态。 这涉及到将 DropDownLists SelectedIndex
属性设置为 0 并清除 Text
TextBox 控件的属性。
注释
如果在返回显示界面之前未将控件返回到其预编辑状态,请考虑会发生什么情况。 用户可以单击“处理产品发货”按钮,输入发货中的产品,然后单击“从发货添加产品”。 这将添加产品并将用户返回到显示界面。 此时,用户可能想要添加另一批货物。 单击“流程产品发货”按钮后,它们将返回到插入界面,但 DropDownList 选择和 TextBox 值仍会填充其以前的值。
Protected Sub AddProducts_Click(sender As Object, e As EventArgs) _
Handles AddProducts.Click
' TODO: Save the products
' Revert to the display interface
ReturnToDisplayInterface()
End Sub
Protected Sub CancelButton_Click(sender As Object, e As EventArgs) _
Handles CancelButton.Click
' Revert to the display interface
ReturnToDisplayInterface()
End Sub
Const firstControlID As Integer = 1
Const lastControlID As Integer = 5
Private Sub ReturnToDisplayInterface()
' Reset the control values in the inserting interface
Suppliers.SelectedIndex = 0
Categories.SelectedIndex = 0
For i As Integer = firstControlID To lastControlID
CType(InsertingInterface.FindControl _
("ProductName" + i.ToString()), TextBox).Text = String.Empty
CType(InsertingInterface.FindControl _
("UnitPrice" + i.ToString()), TextBox).Text = String.Empty
Next
DisplayInterface.Visible = True
InsertingInterface.Visible = False
End Sub
这两个 Click
事件处理程序只是调用 ReturnToDisplayInterface
方法,不过我们将在步骤4再次讨论“从发货 Click
添加产品”事件处理程序,并添加代码来保存产品。
ReturnToDisplayInterface
首先,将 Suppliers
和 Categories
两个下拉列表恢复到其第一个选项。 这两个常量 firstControlID
,并 lastControlID
标记在插入接口中命名产品名称和单价 TextBox 时使用的起始和结束控件索引值,并在循环的边界 For
中使用,该循环将 TextBox 控件的属性设置 Text
回空字符串。 最后,重置面板 Visible
属性,以便隐藏插入接口并显示显示接口。
花点时间在浏览器中测试此页面。 首次访问页面时,应会看到显示界面,如图 5 所示。 单击“流程产品发货”按钮。 页面将回传,现在应该会看到插入界面,如图 12 所示。 单击“从发货添加产品”或“取消”按钮可返回到显示界面。
注释
查看插入界面的界面时,花点时间在单价文本框上测试一下comparevalidators。 单击“从发货添加产品”按钮时,如果货币值无效或价格小于零,您应该会看到客户端消息框中的警告。
图 12:单击“流程产品发货”按钮后显示插入界面(单击以查看全尺寸图像)
步骤 4:添加产品
本教程只剩下将产品保存到数据库中,这是在“从发货按钮添加产品”Click
事件处理程序中完成的。 为此,可以为提供的每个产品名称创建一个 ProductsDataTable
实例并添加一个 ProductsRow
实例。 添加这些ProductsRow
后,我们将调用ProductsBLL
类的UpdateWithTransaction
方法,并传入ProductsDataTable
。 回想一下,在教程《将数据库修改包装在事务中》中创建的UpdateWithTransaction
方法会将 ProductsDataTable
传递给 ProductsTableAdapter
的 UpdateWithTransaction
方法。 在此之后,启动 ADO.NET 事务,TableAdapter 会针对 DataTable 中添加的每个ProductsRow
项向数据库发出INSERT
语句。 假设所有产品都已无误添加,则提交交易,否则将回滚。
“从发货按钮添加产品”事件处理程序的代码 Click
还需要执行一些错误检查。 由于插入界面中没有使用 RequiredFieldValidator,因此用户可以在省略了产品名称的情况下输入产品的价格。 由于需要产品名称,因此,如果出现这种情况,我们需要提醒用户,并停止插入。 完整的 Click
事件处理程序代码如下:
Protected Sub AddProducts_Click(sender As Object, e As EventArgs) _
Handles AddProducts.Click
' Make sure that the UnitPrice CompareValidators report valid data...
If Not Page.IsValid Then Exit Sub
' Add new ProductsRows to a ProductsDataTable...
Dim products As New Northwind.ProductsDataTable()
For i As Integer = firstControlID To lastControlID
' Read in the values for the product name and unit price
Dim productName As String = CType(InsertingInterface.FindControl _
("ProductName" + i.ToString()), TextBox).Text.Trim()
Dim unitPrice As String = CType(InsertingInterface.FindControl _
("UnitPrice" + i.ToString()), TextBox).Text.Trim()
' Ensure that if unitPrice has a value, so does productName
If unitPrice.Length > 0 AndAlso productName.Length = 0 Then
' Display a warning and exit this event handler
StatusLabel.Text = "If you provide a unit price you must also
include the name of the product."
StatusLabel.Visible = True
Exit Sub
End If
' Only add the product if a product name value is provided
If productName.Length > 0 Then
' Add a new ProductsRow to the ProductsDataTable
Dim newProduct As Northwind.ProductsRow = products.NewProductsRow()
' Assign the values from the web page
newProduct.ProductName = productName
newProduct.SupplierID = Convert.ToInt32(Suppliers.SelectedValue)
newProduct.CategoryID = Convert.ToInt32(Categories.SelectedValue)
If unitPrice.Length > 0 Then
newProduct.UnitPrice = Convert.ToDecimal(unitPrice)
End If
' Add any "default" values
newProduct.Discontinued = False
newProduct.UnitsOnOrder = 0
products.AddProductsRow(newProduct)
End If
Next
' If we reach here, see if there were any products added
If products.Count > 0 Then
' Add the new products to the database using a transaction
Dim productsAPI As New ProductsBLL()
productsAPI.UpdateWithTransaction(products)
' Rebind the data to the grid so that the products just added are displayed
ProductsGrid.DataBind()
' Display a confirmation (don't use the Warning CSS class, though)
StatusLabel.CssClass = String.Empty
StatusLabel.Text = String.Format( _
"{0} products from supplier {1} have been " & _
"added and filed under category {2}.", _
products.Count, Suppliers.SelectedItem.Text, Categories.SelectedItem.Text)
StatusLabel.Visible = True
' Revert to the display interface
ReturnToDisplayInterface()
Else
' No products supplied!
StatusLabel.Text =
"No products were added. Please enter the " & _
"product names and unit prices in the textboxes."
StatusLabel.Visible = True
End If
End Sub
事件处理程序首先确保 Page.IsValid
属性返回值 True
。 如果返回 False
,则表示一个或多个 CompareValidator 报告无效数据;在这种情况下,我们不希望尝试插入输入的产品,否则在尝试将用户输入的单价值 ProductsRow
分配给该 UnitPrice
属性时,最终会出现异常。
接下来,将创建一个新 ProductsDataTable
实例(products
)。 循环For
用于遍历产品名称和单价的文本框,将Text
属性读入局部变量productName
和unitPrice
。 如果用户输入了单价但未输入相应的产品名称,则 StatusLabel
显示消息:如果提供单价,则必须包括产品名称,随后事件处理程序退出。
如果已提供产品名称, ProductsRow
将使用 ProductsDataTable
s NewProductsRow
方法创建新实例。 此新的ProductsRow
实例的ProductName
属性被设置为当前产品名称的TextBox,而SupplierID
和CategoryID
属性则分配给插入接口标头中DropDownLists的SelectedValue
属性。 如果用户输入了产品价格的值,则会将其 ProductsRow
分配给实例 UnitPrice
属性;否则,该属性将保持未分配,会导致数据库中的 UnitPrice
值为 NULL
。 最后,Discontinued
和 UnitsOnOrder
属性分别分配给硬编码的值 False
和 0。
将属性分配给ProductsRow
实例后,该实例将被添加到ProductsDataTable
中。
完成 For
循环后,我们会检查是否添加了任何产品。 毕竟,用户可以在输入任何产品名称或价格之前单击“从发货中添加产品”。 如果ProductsDataTable
中至少有一个产品,则调用类ProductsBLL
中的UpdateWithTransaction
方法。 接下来,数据将反弹到 ProductsGrid
GridView,以便新添加的产品将显示在显示界面中。 更新StatusLabel
以显示确认消息,同时调用ReturnToDisplayInterface
以隐藏插入接口并显示展示接口。
如果未输入任何产品,则插入界面仍显示,但消息“未添加产品”。 请在文本框中输入产品名称和单价。
图 13、14 和 15 显示作中的插入和显示接口。 在图 13 中,用户输入了没有相应产品名称的单价值。 图 14 显示了成功添加三个新产品后的显示界面,而图 15 显示了 GridView 中新增的两个产品(第三个产品位于上一页)。
图 13:输入单价时需要产品名称(单击以查看全尺寸图像)
图 14:为供应商 Mayumi s 添加了三个新的蔬菜(单击以查看全尺寸图像)
图 15:可在 GridView 的最后一页中找到新产品(单击以查看全尺寸图像)
注释
本教程中使用的批处理插入逻辑将插入包装在事务范围内。 若要验证这一点,请有意引入数据库级错误。 例如,与其将新 ProductsRow
实例的 CategoryID
属性分配给 DropDownList 中所选的 Categories
值,而是分配给类似 i * 5
的值。 下面是 i
循环索引器,其值范围为 1 到 5。 因此,在批量插入两个或多个产品时,第一个产品将具有有效的CategoryID
值(5),但后续产品的CategoryID
值与Categories
表中的CategoryID
值不匹配。 净效果是,第一个INSERT
会成功,但随后的操作会因外键约束冲突而失败。 由于批量插入是原子的,因此第一个 INSERT
将回滚,使数据库恢复到开始批量插入过程之前的状态。
概要
在此教程和前面的两个教程中,我们创建了允许更新、删除和插入批处理数据的接口,所有这些接口都使用了我们在事务教程中的 包装数据库修改 中添加到数据访问层的事务支持。 在某些情况下,此类批处理用户界面通过减少点击数、回发和键盘到鼠标上下文开关,同时保持基础数据的完整性,极大地提高了最终用户的效率。
本教程介绍了如何使用批处理数据。 下一组教程探讨各种高级数据访问层方案,包括使用 TableAdapter 方法中的存储过程、在 DAL 中配置连接和命令级设置、加密连接字符串等!
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别致谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是希尔顿·吉森诺和索伦·雅各布·劳里森。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com