批量插入 (VB)

作者 :斯科特·米切尔

下载 PDF

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

介绍

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

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

只需稍作工作,即可创建一个批处理插入界面,使用户能够选择供应商和类别一次,输入一系列产品名称和单价,然后单击一个按钮将新产品添加到数据库(请参阅图 1)。 添加每个产品时,会将其ProductNameUnitPrice数据字段分配为 TextBoxes 中输入的值,并且将其CategoryIDSupplierID的值分配为窗体顶部 DropDownLists 中的值。 DiscontinuedUnitsOnOrder的值分别被设置为硬编码值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 属性IDProductsGrid设置为其智能标记,并将其绑定到名为 ProductsDataSource 的新 ObjectDataSource。 将 ObjectDataSource 配置为从 ProductsBLL 类 s GetProducts 方法拉取其数据。 由于此 GridView 仅用于显示数据,因此请将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为“无”。 单击“完成”以完成“配置数据源”向导。

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

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

将 UPDATE、INSERT 和 DELETE 选项卡中的 Drop-Down 列表设置为(无)

图 4:将 UPDATE、INSERT 和 DELETE 选项卡中的“Drop-Down 列表”设置为“无”(单击可查看全尺寸图像

完成 ObjectDataSource 向导后,Visual Studio 将为产品数据字段添加 BoundFields 和 CheckBoxField。 保留ProductNameCategoryNameSupplierNameUnitPriceDiscontinued字段,删除其他所有字段。 随意进行任何审美自定义。 我决定将 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 上方的“流程产品发货”按钮,该按钮一次列出十个产品。

GridView 列出了产品并提供排序和分页功能

图 5:GridView 列出了提供排序和分页功能的产品列表(单击可查看全尺寸图像

步骤 2:创建插入接口

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

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

接下来,我们需要创建如图 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 是用于“添加发货产品”和“取消”按钮的页脚行,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;
}

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

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

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

我们现在已准备好将 Web 控件添加到插入界面。 将两个下拉列表从工具箱拖到表中的指定单元格内,一个用于供应商,一个用于类别。

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

将 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 方法;将 UPDATE 和 DELETE 选项卡中的下拉列表设置为 “无”,然后单击“完成”以完成向导。 最后,让 DropDownList 显示 CategoryName 数据字段,并使用 CategoryID 该值作为值。

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

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

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

现在,我们需要创建 TextBoxes 来收集每个新产品的名称和价格。 将一个 TextBox 控件从工具箱拖动到设计器中,用于五个产品名称和价格行中的每一行。 将 ID TextBoxes 的属性设置为 ProductName1UnitPrice1ProductName2UnitPrice2ProductName3UnitPrice3等。

在每个单价 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 属性AddProductsCancelButtonText属性设置为“从发货和取消添加产品”。 此外,将 CancelButton 控件 CausesValidation 的属性设置为 false.

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

将标签 Web 控件从工具箱拖到设计器中的页面顶部。 将ID属性设置为StatusLabel,清除Text属性,并将属性EnableViewState设置为 VisibleFalse。 正如我们在前面的教程中看到的那样,设置EnableViewState属性为False允许我们通过程序更改Label的属性值,并在后续回发时自动恢复为默认值。 这简化了代码,用于在某些用户动作后显示状态消息,而这些消息会在随后的回发中消失。 最后,将 StatusLabel 控件的属性 CssClass 设置为 Warning,这是一个 CSS 类的名称,该类以 Styles.css 大、斜体、粗体、红色字体显示文本。

图 11 显示了在添加和配置 Label 控件后的 Visual Studio 设计器。

将 StatusLabel 控件置于两个面板控件上方

图 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 首先,将 SuppliersCategories 两个下拉列表恢复到其第一个选项。 这两个常量 firstControlID ,并 lastControlID 标记在插入接口中命名产品名称和单价 TextBox 时使用的起始和结束控件索引值,并在循环的边界 For 中使用,该循环将 TextBox 控件的属性设置 Text 回空字符串。 最后,重置面板 Visible 属性,以便隐藏插入接口并显示显示接口。

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

注释

查看插入界面的界面时,花点时间在单价文本框上测试一下comparevalidators。 单击“从发货添加产品”按钮时,如果货币值无效或价格小于零,您应该会看到客户端消息框中的警告。

单击“流程产品发货”按钮后,将显示插入界面

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

步骤 4:添加产品

本教程只剩下将产品保存到数据库中,这是在“从发货按钮添加产品”Click事件处理程序中完成的。 为此,可以为提供的每个产品名称创建一个 ProductsDataTable 实例并添加一个 ProductsRow 实例。 添加这些ProductsRow后,我们将调用ProductsBLL类的UpdateWithTransaction方法,并传入ProductsDataTable。 回想一下,在教程《将数据库修改包装在事务中》中创建的UpdateWithTransaction 方法会将 ProductsDataTable 传递给 ProductsTableAdapterUpdateWithTransaction 方法。 在此之后,启动 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属性读入局部变量productNameunitPrice。 如果用户输入了单价但未输入相应的产品名称,则 StatusLabel 显示消息:如果提供单价,则必须包括产品名称,随后事件处理程序退出。

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

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

完成 For 循环后,我们会检查是否添加了任何产品。 毕竟,用户可以在输入任何产品名称或价格之前单击“从发货中添加产品”。 如果ProductsDataTable中至少有一个产品,则调用类ProductsBLL中的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 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。

特别致谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是希尔顿·吉森诺和索伦·雅各布·劳里森。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com