批量插入 (C#)

作者 :斯科特·米切尔

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

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

注释

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

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

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

步骤 4:添加产品

本教程只剩下将产品保存到数据库中,这是在“从发货按钮添加产品”Click事件处理程序中完成的。 为此,可以为提供的每个产品名称创建一个 ProductsDataTable 实例并添加一个 ProductsRow 实例。 添加这些ProductsRow后,我们将调用ProductsBLL类的UpdateWithTransaction方法,并传入ProductsDataTable。 回想一下,该方法 UpdateWithTransaction 是在 Wrapping Database Modifications within a Transaction 教程中创建的 ProductsDataTable ,它将 to 方法 ProductsTableAdapter 传递给 s UpdateWithTransaction 方法。 在此之后,启动 ADO.NET 事务,TableAdapter 会针对 DataTable 中添加的每个INSERT项向数据库发出ProductsRow语句。 假设所有产品都已无误添加,则提交交易,否则将回滚。

“从发货按钮添加产品”事件处理程序的代码 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用于遍历产品名称和单价的文本框,将Text属性读入局部变量productNameunitPrice。 如果用户输入了单价但未输入相应的产品名称,则 StatusLabel 显示消息:如果提供单价,则必须包括产品名称,随后事件处理程序退出。

如果已提供产品名称, ProductsRow 将使用 ProductsDataTable s NewProductsRow 方法创建新实例。 此新的ProductsRow实例的ProductName属性被设置为当前产品名称的TextBox,而SupplierIDCategoryID属性则分配给插入接口标头中DropDownLists的SelectedValue属性。 如果用户输入了产品价格的值,则会将其 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值与CategoryID表中的Categories值不匹配。 净效果是,第一个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