批量插入 (C#)

作者 :Scott Mitchell

下载 PDF

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

简介

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

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

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

Batch Inserting 接口

图 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 类 s GetProducts 方法拉取其数据。 由于此 GridView 仅用于显示数据,因此请将“更新”、“插入”和“删除”选项卡中的下拉列表设置为“无”) (。 单击“完成”以完成“配置数据源”向导。

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

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

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

图 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;
}

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

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

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

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

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

配置 ObjectDataSource 以使用 SuppliersBLL 类 GetSuppliers 方法

图 7:将 ObjectDataSource 配置为使用 SuppliersBLL 类 s 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 属性设置为 GreaterThanEqualValueToCompare 将 设置为 0,将 Type 设置为 Currency。 这些设置指示 CompareValidator 确保输入的价格是大于或等于零的有效货币值。 将 Text 属性设置为 *,并将 ErrorMessage 设置为 价格必须大于或等于零。 此外,请省略任何货币符号。

注意

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

目前,显示界面可见,但插入界面处于隐藏状态。 这是因为 Panel DisplayInterface 的 属性设置为 true (默认值) ,而 InsertingInterface Panel 的 Visible 属性设置为 falseVisible 。 若要在两个接口之间切换,只需切换每个控件的 Visible 属性值。

我们希望在单击“处理产品发货”按钮时,从显示界面移动到插入界面。 因此,请为此 Button 事件创建包含以下代码的 Click 事件处理程序:

protected void ProcessShipment_Click(object sender, EventArgs e)
{
    DisplayInterface.Visible = false;
    InsertingInterface.Visible = true;
}

此代码只是隐藏面板 DisplayInterface 并显示 InsertingInterface 面板。

接下来,在插入界面中为“从发货添加产品”和“取消按钮”控件创建事件处理程序。 单击其中任一按钮时,需要还原回到显示界面。 为两个 Button 控件创建 Click 事件处理程序,以便它们调用 ReturnToDisplayInterface,我们将暂时添加一个方法。 除了隐藏 InsertingInterface 面板并显示 DisplayInterface 面板外 ReturnToDisplayInterface ,该方法还需要将 Web 控件返回到其预编辑状态。 这涉及到将 DropDownLists SelectedIndex 属性设置为 0 并清除 Text TextBox 控件的属性。

注意

请考虑在返回到显示界面之前,如果未将控件返回到其预编辑状态,会发生什么情况。 用户可以单击“处理产品发货”按钮,输入发货中的产品,然后单击“从发货添加产品”。 这将添加产品并将用户返回到显示界面。 此时,用户可能需要添加另一批货物。 单击“处理产品发货”按钮后,它们将返回到插入界面,但 DropDownList 选择和 TextBox 值仍将填充其以前的值。

protected void AddProducts_Click(object sender, EventArgs e)
{
    // TODO: Save the products
    // Revert to the display interface
    ReturnToDisplayInterface();
}
protected void CancelButton_Click(object sender, EventArgs e)
{
    // Revert to the display interface
    ReturnToDisplayInterface();
}
const int firstControlID = 1;
const int lastControlID = 5;
private void ReturnToDisplayInterface()
{
    // Reset the control values in the inserting interface
    Suppliers.SelectedIndex = 0;
    Categories.SelectedIndex = 0;
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        ((TextBox)InsertingInterface.FindControl("ProductName" + i.ToString())).Text =
            string.Empty;
        ((TextBox)InsertingInterface.FindControl("UnitPrice" + i.ToString())).Text = 
            string.Empty;
    }
    DisplayInterface.Visible = true;
    InsertingInterface.Visible = false;
}

这两个 Click 事件处理程序都只是调用 ReturnToDisplayInterface 方法,不过我们将返回到步骤 4 中的“从发货 Click 添加产品”事件处理程序,并添加代码以保存产品。 ReturnToDisplayInterface 首先将 SuppliersCategories DropDownLists 返回到其第一个选项。 这两个常量firstControlIDlastControlID标记开始和结束控件索引值,用于命名插入接口中的产品名称和单价 TextBoxs,并在将 TextBox 控件的属性设置Text回空字符串的for循环边界中使用。 最后,重置 Panels Visible 属性,以便隐藏插入接口并显示显示界面。

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

注意

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

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

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

步骤 4:添加产品

本教程剩下的就是在“从发货添加产品”按钮事件处理程序 Click 中将产品保存到数据库。 为此,可以创建 ProductsDataTable 并为提供的每个产品名称添加 实例 ProductsRow 。 添加这些 ProductsRow 后,我们将调用ProductsBLL传入 ProductsDataTable的类 方法UpdateWithTransactionUpdateWithTransaction回想一下,在事务中包装数据库修改教程中创建的 方法将 传递给 ProductsDataTableProductsTableAdapter s UpdateWithTransaction 方法。 从该处,将启动 ADO.NET 事务,并且 TableAdapter 针对 DataTable 中添加的每个ProductsRow操作向数据库发出INSERT语句。 假设添加所有产品且未出错,则会提交事务,否则将回滚该事务。

“从发货按钮添加产品”事件处理程序的代码 Click 还需要执行一些错误检查。 由于插入界面中未使用 RequiredFieldValidator,因此用户可以输入产品的价格,同时省略其名称。 由于产品名称是必需的,因此,如果出现这种情况,我们需要提醒用户,而不是继续插入。 完整的 Click 事件处理程序代码如下:

protected void AddProducts_Click(object sender, EventArgs e)
{
    // Make sure that the UnitPrice CompareValidators report valid data...
    if (!Page.IsValid)
        return;
    // Add new ProductsRows to a ProductsDataTable...
    Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
    for (int i = firstControlID; i <= lastControlID; i++)
    {
        // Read in the values for the product name and unit price
        string productName = ((TextBox)InsertingInterface.FindControl
            ("ProductName" + i.ToString())).Text.Trim();
        string unitPrice = ((TextBox)InsertingInterface.FindControl
            ("UnitPrice" + i.ToString())).Text.Trim();
        // Ensure that if unitPrice has a value, so does productName
        if (unitPrice.Length > 0 && productName.Length == 0)
        {
            // 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;
            return;
        }
        // Only add the product if a product name value is provided
        if (productName.Length > 0)
        {
            // Add a new ProductsRow to the ProductsDataTable
            Northwind.ProductsRow newProduct = 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)
                newProduct.UnitPrice = Convert.ToDecimal(unitPrice);
            // Add any "default" values
            newProduct.Discontinued = false;
            newProduct.UnitsOnOrder = 0;
            products.AddProductsRow(newProduct);
        }
    }
    // If we reach here, see if there were any products added
    if (products.Count > 0)
    {
        // Add the new products to the database using a transaction
        ProductsBLL productsAPI = 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;
    }
}

事件处理程序首先确保 Page.IsValid 属性返回值 true。 如果它返回 false,则表示一个或多个 CompareValidator 报告无效数据;在这种情况下,我们不想尝试插入输入的产品,或者在尝试将用户输入的单价值 ProductsRow 分配给 属性 UnitPrice 时,最终会出现异常。

接下来, ProductsDataTable () products 创建新实例。 循环 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 调用 类方法 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放置一行。