批量插入 (VB)

作者 :Scott Mitchell

下载 PDF

了解如何在单个操作中插入多个数据库记录。 在用户界面层中,我们扩展了 GridView,以允许用户输入多个新记录。 在数据访问层中,我们将多个 Insert 操作包装在一个事务中,以确保所有插入都成功或回滚所有插入。

简介

批量更新 教程中,我们介绍了如何自定义 GridView 控件,以呈现可编辑多个记录的接口。 访问页面的用户可以进行一系列更改,然后单击一个按钮即可执行批量更新。 对于用户通常一次性更新许多记录的情况,与 插入、更新和删除数据概述 教程中首次探索的默认每行编辑功能相比,此类界面可以保存无数次单击和键盘到鼠标上下文切换。

添加记录时也可以应用此概念。 想象一下,在 Northwind Traders,我们通常从包含特定类别许多产品的供应商那里收到发货。 例如,我们可能会收到来自东京贸易商的六种不同茶和咖啡产品的发货。 如果用户通过 DetailsView 控件一次输入一个产品,他们将必须反复选择许多相同的值:他们需要选择相同的类别 (饮料) ,相同的供应商 (Tokyo Traders) ,相同的停产值 (False) ,订单值 (0) 相同的单位。 这种重复的数据输入不仅耗时,而且容易出错。

只需一点点工作,我们可以创建一个批量插入界面,使用户能够选择供应商和类别一次,输入一系列产品名称和单价,然后单击按钮以将新产品添加到数据库 (请参阅图 1) 。 添加每个产品后,会 ProductName 为其 和数据 UnitPrice 字段分配在 TextBoxes 中输入的值,而其 CategoryIDSupplierID 值则分配来自表单顶部的 DropDownLists 的值。 和 DiscontinuedUnitsOnOrder 值分别设置为 和 0 的 False 硬编码值。

批处理插入接口

图 1:批量插入接口 (单击以查看全尺寸图像)

在本教程中,我们将创建一个实现批量插入接口的页面,如图 1 所示。 与前面的两个教程一样,我们将插入操作包装在事务范围内,以确保原子性。 让我们开始吧!

步骤 1:创建显示界面

本教程将包含一个页面,该页面分为两个区域:显示区域和插入区域。 我们将在此步骤中创建的显示界面显示 GridView 中的产品,并包含标题为“处理产品发货”的按钮。 单击此按钮时,显示界面将替换为插入界面,如图 1 所示。 单击“从发货添加产品”或“取消”按钮后,显示界面将返回。 我们将在步骤 2 中创建插入接口。

创建具有两个接口(一次只显示其中一个接口)的页面时,每个接口通常放置在 Panel Web 控件中,该控件用作其他控件的容器。 因此,我们的页面将具有两个面板控件,每个接口各有一个。

首先打开 文件夹中的页面BatchInsert.aspxBatchData,并将“面板”从“工具箱”拖到Designer (请参阅图 2) 。 将 Panel 属性 ID 设置为 DisplayInterface。 将面板添加到Designer时,其 HeightWidth 属性分别设置为 50px 和 125px。 从属性窗口中清除这些属性值。

将“面板”从“工具箱”拖到Designer

图 2:将面板从工具箱拖到Designer (单击以查看全尺寸图像)

接下来,将 Button 和 GridView 控件拖动到面板中。 将 Button 属性 ID 设置为 ProcessShipment ,将其 Text 属性设置为“处理产品发货”。 将 GridView 属性 ID 设置为 ProductsGrid ,并从其智能标记中将其绑定到名为 ProductsDataSource的新 ObjectDataSource。 将 ObjectDataSource 配置为从 ProductsBLL 类方法 GetProducts 中提取其数据。 由于此 GridView 仅用于显示数据,因此请将“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 。 单击“完成”以完成“配置数据源”向导。

显示从 ProductsBLL 类 GetProducts 方法返回的数据

图 3:显示从 ProductsBLL 类方法 GetProducts 返回的数据 (单击以查看全尺寸图像)

将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“ (None”)

图 4:将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“无 () (单击以查看全尺寸图像)

完成 ObjectDataSource 向导后,Visual Studio 将为产品数据字段添加 BoundFields 和 CheckBoxField。 删除除 、、CategoryNameSupplierNameUnitPriceDiscontinued 字段之外ProductName的所有字段。 随意进行任何美观自定义。 我决定将 UnitPrice 字段的格式设置为货币值,对字段重新排序,并重命名了多个字段 HeaderText 值。 此外,通过选中 GridView 智能标记中的“启用分页”和“启用排序”复选框,将 GridView 配置为包含分页和排序支持。

添加 Panel、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> 标记中。 由于这些控件位于 Panel 内 DisplayInterface ,因此只需将 Panel 属性 Visible 设置为 False即可隐藏它们。 步骤 3 查看以编程方式更改 Panel 属性 Visible ,以响应按钮单击以显示一个界面,同时隐藏另一个界面。

花点时间通过浏览器查看进度。 如图 5 所示,应在 GridView 上方看到一个“处理产品发货”按钮,该按钮一次列出 10 个产品。

GridView Lists产品和产品/服务排序和分页功能

图 5:GridView Lists产品和产品/服务排序和分页功能 (单击以查看全尺寸图像)

步骤 2:创建插入接口

显示界面完成后,我们准备创建插入接口。 在本教程中,让我们创建一个插入界面,提示输入单个供应商和类别值,然后允许用户输入最多五个产品名称和单价值。 使用此界面,用户可以添加一到五个新产品,这些产品都共享相同的类别和供应商,但具有唯一的产品名称和价格。

首先,将“工具箱”中的“面板”拖到Designer,并将其置于现有DisplayInterface面板下方。 ID将此新添加的 Panel 的 属性设置为 InsertingInterface ,并将其 Visible 属性设置为 False。 我们将在步骤 3 中添加将 Panel 属性Visible设置为 InsertingInterfaceTrue 的代码。 此外,请清除 Panel 和 HeightWidth 属性值。

接下来,我们需要创建如图 1 所示的插入接口。 此接口可以通过各种 HTML 技术创建,但我们将使用一个相当简单的方法:一个四列七行的表。

注意

输入 HTML <table> 元素的标记时,我更喜欢使用“源”视图。 虽然 Visual Studio 确实具有通过Designer添加<table>元素的工具,但Designer似乎太愿意将未分配style的设置注入到标记中。 创建<table>标记后,通常返回到Designer添加 Web 控件并设置其属性。 创建具有预先确定的列和行的表时,我更喜欢使用静态 HTML 而不是 表 Web 控件 ,因为放置在表 Web 控件中的任何 Web 控件只能使用 FindControl("controlID") 模式访问。 但是,我确实将表 Web 控件用于动态大小的表 (行或列基于某些数据库或用户指定的条件的表) ,因为表 Web 控件可以通过编程方式构造。

在面板的InsertingInterface标记中<asp:Panel>输入以下标记:

<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对于供应商和类别 DropDownLists 将转到的标题行;BatchInsertFooterRow对于从发货添加产品和取消按钮将转到的页脚行;以及将包含产品和单价 TextBox 控件的行的交替BatchInsertRowBatchInsertAlternatingRow和值。 我在 文件中创建了相应的 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;
}

输入此标记后,返回到“设计”视图。 这<table>在Designer中应显示为一个四列七行表,如图 6 所示。

插入接口由四列 Seven-Row 表组成

图 6:插入接口由四列 Seven-Row 表组成, (单击以查看全尺寸图像)

现在,我们已准备好将 Web 控件添加到插入界面。 将两个 DropDownList 从“工具箱”拖动到表中的相应单元格中,一个用于供应商,一个用于类别。

将供应商 DropDownList 属性 ID 设置为 Suppliers ,并将其绑定到名为 SuppliersDataSource的新 ObjectDataSource。 配置新的 ObjectDataSource 以从 SuppliersBLL 类方法 GetSuppliers 检索其数据,并将“更新”选项卡下拉列表设置为 (“无”) 。 单击“完成”以完成向导。

将 ObjectDataSource 配置为使用 SuppliersBLL 类 GetSuppliers 方法

图 7:配置 ObjectDataSource 以使用 SuppliersBLL 类方法 GetSuppliers (单击以查看全尺寸图像)

Suppliers让 DropDownList 显示CompanyName数据字段,并将SupplierID数据字段用作其 ListItem 值。

显示 CompanyName 数据字段,并使用 SupplierID 作为值

图 8:显示数据 CompanyName 字段并使用 SupplierID 作为值 (单击以查看全尺寸图像)

将第二个 DropDownList Categories 命名为 ,并将其绑定到名为 CategoriesDataSource的新 ObjectDataSource。 将 CategoriesDataSource ObjectDataSource 配置为使用 CategoriesBLL 类 s GetCategories 方法;将“更新”和“删除”选项卡中的下拉列表设置为 (“无”) 并单击“完成”完成向导。 最后,让 DropDownList 显示 CategoryName 数据字段,并使用 CategoryID 作为值。

添加这两个 DropDownList 并将其绑定到适当配置的 ObjectDataSources 后,屏幕应类似于图 9。

标题行现在包含供应商和类别下拉列表

图 9:标题行现在包含 SuppliersCategories DropDownLists (单击以查看全尺寸图像)

我们现在需要创建 TextBox 来收集每个新产品的名称和价格。 将一个 TextBox 控件从工具箱拖到五个产品名称和价格行的Designer。 将 ID TextBox 的属性设置为 ProductName1、、UnitPrice1ProductName2UnitPrice2ProductName3UnitPrice3、 等。

在每个单价 TextBox 之后添加 CompareValidator,并将 ControlToValidate 属性设置为相应的 ID。 此外,将 Operator 属性设置为 GreaterThanEqual、将 ValueToCompare 设置为 0 和 TypeCurrency 这些设置指示 CompareValidator 确保输入的价格是大于或等于零的有效货币值。 将 Text 属性设置为 *,将 ErrorMessage 设置为 价格必须大于或等于零。 此外,请省略任何货币符号。

注意

插入接口不包括任何 RequiredFieldValidator 控件,即使数据库表中的 ProductNameProducts 字段不允许 NULL 值。 这是因为我们希望允许用户输入最多五个产品。 例如,如果用户要提供前三行的产品名称和单价,将最后两行留空,我们只需向系统添加三个新产品。 但是,由于 ProductName 是必需的,因此我们需要以编程方式检查,以确保如果输入单价,则提供相应的产品名称值。 我们将在步骤 4 中解决此检查。

验证用户输入时,如果值包含货币符号,CompareValidator 会报告无效数据。 在每个单价 TextBox 的前面添加 $,作为视觉提示,指示用户在输入价格时省略货币符号。

最后,在 Panel 中添加 InsertingInterface ValidationSummary 控件,将其 ShowMessageBox 属性设置为 True ,将其 ShowSummary 属性设置为 False。 使用这些设置时,如果用户输入了无效的单价值,则会在有问题的 TextBox 控件旁边显示星号,ValidationSummary 将显示一个客户端消息框,其中显示了我们之前指定的错误消息。

此时,屏幕应类似于图 10。

插入界面现在包括产品名称和价格的文本框

图 10:插入界面现在包括产品名称和价格的文本框 (单击以查看全尺寸图像)

接下来,我们需要将“从发货添加产品”和“取消”按钮添加到页脚行。 将两个按钮控件从工具箱拖到插入界面的页脚中,分别将 Buttons ID 属性 AddProducts 设置为 和 CancelButton ,并将 Text 属性设置为“从发货添加产品”和“取消”。 此外,将 CancelButton 控件的 CausesValidation 属性设置为 false

最后,我们需要添加一个标签 Web 控件,该控件将显示这两个接口的状态消息。 例如,当用户成功添加新的产品发货时,我们希望返回到显示界面并显示确认消息。 但是,如果用户提供了新产品的价格,但未显示产品名称,我们需要显示警告消息, ProductName 因为字段是必需的。 由于我们需要为两个接口显示此消息,因此请将其放在页面顶部面板外部。

将“标签 Web”控件从“工具箱”拖到Designer页面顶部。 将 属性设置为 StatusLabel,清除 Text 属性,并将 和 EnableViewState 属性设置为 VisibleFalseID 正如我们在前面的教程中看到的,将 属性设置为 EnableViewStateFalse 允许我们以编程方式更改 Label 的属性值,并在后续回发时将它们自动还原回默认值。 这简化了显示状态消息的代码,以响应在后续回发中消失的某些用户操作。 最后,将 StatusLabel 控件 属性 CssClass 设置为 Warning,这是在 中 Styles.css 定义的 CSS 类的名称,该类以大斜体、粗体、红色字体显示文本。

图 11 显示了添加和配置标签后 Visual Studio Designer。

将 StatusLabel 控件放在两个面板控件的上方

图 11:将 StatusLabel 控件置于两个面板控件上方 (单击以查看全尺寸图像)

步骤 3:在显示接口和插入接口之间切换

此时,我们已经完成了显示和插入接口的标记,但仍剩下两个任务:

  • 在显示接口和插入接口之间切换
  • 将发货中的产品添加到数据库

目前,显示界面可见,但插入界面处于隐藏状态。 这是因为 Panel DisplayInterface 属性 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 Panel 和显示 DisplayInterface Panel 外 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 DropDownLists 返回到其第一个选项。 两个常量 firstControlIDlastControlID 标记开始和结束控件索引值,用于在插入接口中命名产品名称和单价 TextBox,并用于循环边界 For ,将 TextBox 控件的属性设置 Text 回空字符串。 最后,重置 Panels Visible 属性,以便隐藏插入接口并显示显示界面。

花点时间在浏览器中测试此页面。 首次访问页面时,应会看到如图 5 所示的显示界面。 单击“处理产品发货”按钮。 页面将回发,现在应看到插入界面,如图 12 所示。 单击“从发货添加产品”或“取消”按钮会将您返回到显示界面。

注意

查看插入界面时,请花点时间测试 TextBoxs 单价上的 CompareValidator。 单击“从发货添加产品”按钮时,应看到客户端消息框警告,其中货币值或价格值小于零。

单击“处理产品发货”按钮后会显示插入界面

图 12:单击“处理产品发货”按钮后显示插入界面 (单击以查看全尺寸图像)

步骤 4:添加产品

本教程剩下的就是在“从发货按钮添加产品” Click 事件处理程序中将产品保存到数据库。 为此,可以创建 ProductsDataTable 并为提供的每个产品名称添加实例 ProductsRow 。 添加这些 ProductsRow 后,我们将调用ProductsBLL传入 ProductsDataTable的 类方法UpdateWithTransactionUpdateWithTransaction回想一下,在事务中包装数据库修改教程中创建的 方法将 传递给 ProductsDataTableProductsTableAdapterUpdateWithTransaction 方法。 从该处开始 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 分配给 s UnitPrice 属性时,最终会出现异常。

接下来,) (products 创建新ProductsDataTable实例。 循环 For 用于循环访问产品名称和单价 TextBox,并将 Text 属性读入局部变量 productNameunitPrice。 如果用户为单价输入了值,但未输入相应的产品名称,则会 StatusLabel 显示消息:如果提供单价,还必须包括产品名称,并且事件处理程序退出。

如果已提供产品名称,则使用 ProductsDataTable s NewProductsRow 方法创建新ProductsRow实例。 此新 ProductsRow 实例 的 ProductName 属性设置为当前产品名称 TextBox, SupplierID 而 和 CategoryID 属性则 SelectedValue 分配给插入接口标头中的 DropDownLists 的属性。 如果用户输入了产品价格的值,则会将其分配给 ProductsRow 实例的 属性 UnitPrice ;否则,该属性将保持未分配状态,这将导致 NULL 数据库中的值 UnitPrice 。 最后, DiscontinuedUnitsOnOrder 属性分别分配给硬编码值 False 和 0。

将属性分配给实例后,ProductsRow该属性将添加到 。ProductsDataTable

循环完成后For,我们检查是否已添加任何产品。 毕竟,用户可能已单击“从发货添加产品”,然后输入任何产品名称或价格。 如果 中 ProductsDataTable至少有一个积,则 ProductsBLL 调用 类 s UpdateWithTransaction 方法。 接下来,数据将反弹到 ProductsGrid GridView,以便新添加的产品将显示在显示界面中。 将 StatusLabel 更新 以显示确认消息,并 ReturnToDisplayInterface 调用 ,隐藏插入接口并显示显示接口。

如果未输入任何产品,则仍显示插入界面,但消息“未添加产品”。 请在显示的文本框中输入产品名称和单价。

图 13、14 和 15 显示了操作中的插入和显示接口。 在图 13 中,用户输入的单价值没有相应的产品名称。 图 14 显示了成功添加三个新产品后的显示界面,而图 15 显示了 GridView 中新增的两个产品 (第三个产品位于上一页) 。

输入单价时需要产品名称

图 13:输入单价时需要产品名称 (单击以查看全尺寸图像)

为供应商 Mayumi s 添加了三个新蔬菜

图 14:已为供应商 Mayumi s 添加了三个新蔬菜 (单击以查看全尺寸图像)

可在 GridView 的最后一页中找到新产品

图 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 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是希尔顿·吉塞诺和 S ren Jacob Lauritsen。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。