批量插入65

简介

在批量更新 教程中 , 我们探讨了如何自定义 GridView 控件来提供可编辑多条记录的界面。访问该页面的用户可以进行一系列修改,然后单击一个按钮,执行批量更新。对于用户需要在一次操作中更新多条记录的情况,与我们在 数据插入、更新和删除概述 教程中探讨的对每行进行编辑的功能相比,这样的界面可减少单击次数以及从键盘到鼠标的转换次数。

在添加记录时,我们也可以利用这种概念。假设在 Northwind Traders 数据库中,我们一般接收拥有特定类别的数个产品的供应商的货物。例如,我们接收 Tokyo Traders 发来的六种不同的茶和咖啡产品。如果用户在 DetailsView 控件中一次输入一种产品,他将需要重复地选择很多相同的值,例如:相同的类别 (Beverages) 、相同的供应商 (Tokyo Traders) 、相同的断货值 (False) 以及相同的订单单位值 (0) 。重复地输入这些数据不仅耗时,也容易出错。

只需要多做一些工作,我们就可以创建一个批量插入界面,允许用户选择一次供应商和类别,输入一系列产品名称和单价,然后单击一个按钮来将这些新产品添加到数据库(参见图 1 )。每添加一个产品时,产品的 ProductName 和 UnitPrice 数据字段的值为用户在文本框中输入的值,而它的 CategoryID 和 SupplierID 值是表格上方的下拉列表中的值。 Discontinued 和 UnitsOnOrder 的值分别被设置为固定值 false 和 0 。

图1 : 批量插入界面

在本教程中 , 我们将创建一个实现批量插入界面的页面 , 如图1 所示。与前面两篇教程相同,我们将在一个事务中封装插入操作,从而确保原子性。让我们开始吧 !

步骤1 : 创建显示界面

本教程创建的页面将包含两个区域:显示区域和插入区域。我们在这一步中创建的是显示界面,它包含一个显示产品的 GridView 以及一个名为 “Process Product Shipment” 的按钮。单击该按钮时 , 显示界面将被替换为如图 1 所示的插入界面。单击 “Add Products from Shipment” 或 “Cancel” 按钮后将返回显示界面。我们将在步骤 2 中创建插入界面。

创建含有两个界面的页面且每次只能看到其中一个界面时 , 我们会使用一个 Web 面板控件 ( 用作其它控件的容器 ) 来放置每个界面。因此,我们的页面将有两个面板控件 – 每个界面一个。

首先,打开 BatchData 文件夹中的 BatchInsert.aspx 页面,从工具箱中将一个面板控件拖放到设计器中(参见图 2 )。将该控件的 ID 属性设为 DisplayInterface 。将该面板控件添加到设计器中时,其 Height 和 Width 属性分别被设为 50px 和 125px 。在 Properties 窗口中清除这些属性值。

图2 : 从工具箱将一个面板控件拖放到设计器中

然后将一个按钮控件和一个 GridView 控件拖放到面板中。将按钮的 ID 属性设为 ProcessShipment , Text 属性设为 “Process Product Shipment” 。将 GridView 的 ID 属性设为 ProductsGrid ,并从智能标记中,将它绑定到一个名为 ProductsDataSource 的新 ObjectDataSource 。配置 ObjectDataSource ,从 ProductsBLL 类的 GetProducts 方法中获取数据。由于该 GridView 仅用于显示数据,因此我们将 UPDATE 、 INSERT 和 DELETE 选项卡中的下拉列表设置为 “(None)” 。单击 Finish 完成 Configure Data Source 向导。

图3 : 显示 ProductsBLL 类的 GetProducts 方法返回的数据

图4 : 将 UPDATE 、INSERT 和 DELETE 选项卡中的下拉列表设置为 “(None)”

完成ObjectDataSource 向导后 ,Visual Studio 将为产品数据字段添加 BoundField 和一个 CheckBoxField 。保留 ProductName 、CategoryName 、SupplierName 、UnitPrice 和 Discontinued 字段 , 将其它字段删除。你可以随意进行外观上的修改。我将 UnitPrice 字段的格式设置为货币值,重新对字段进行了排序,又对一些字段的 HeaderText 值进行了修改。然后通过在 GridView 的智能标记中选中 “Enable Paging” 和 “Enable Sorting” 复选框,配置 GridView 支持分页和排序功能。

添加了面板、按钮、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>

注意 , 按钮和GridView 的标记出现在 <asp:Panel> 标记内部。由于这些控件位于 DisplayInterface 面板内,我们可简单地将面板的 Visible 属性设为 false 来隐藏这些控件。步骤 3 将探讨如何通过编码来改变面板的 Visible 属性,达到单击一个按钮时显示一个界面并隐藏另一个界面的目的。

现在,让我们花些时间在浏览器中查看一下进度。如图 5 所示,在一次显示 10 件产品的 GridView 的上方,你会看到一个名为 “Process Product Shipment” 的按钮。

图5 :GridView 列出产品并提供排序和分页功能

步骤2 : 创建插入界面

完成显示界面后 , 我们将创建插入界面。在本教程中,我们创建的插入界面将提示用户选择一个供应商和一种类别,并允许用户输入最多 5 个产品名称和单价。利用该界面,用户可添加 1 到 5 种具有相同类别和供应商的新产品,但这些产品的名称和价格不同。

首先,从工具箱中将一个面板控件拖放到设计器中,将它放在现有的 DisplayInterface 面板的下方。将该面板控件的 ID 属性设为 InsertingInterface ,并将其 Visible 属性设为 false 。我们将在步骤 3 中增加将 InsertingInterface 面板的 Visible 属性设为 true 的代码。同样,清除该面板的 Height 和 Width 属性值。

然后 , 我们需要创建如图 1 所示的插入界面。该界面可通过一些 HTML 技术创建,但我们将使用一种更简单的方法:创建一个 4 列 7 行的表。

注意 : 在添加 HTML <table> 元素的标记时 , 我倾向于使用 Source 视图。虽然 Visual Studio 的设计器中有添加 <table> 元素的工具 , 但该设计器容易在标记中引入不需要的样式设置。创建 <table> 标记后,我通常会返回到设计器,增加 Web 控件并设置它们的属性。在创建预先确定好列数和行数的表时,我倾向于使用静态 HTML (而非 Web 表控件 ),因为我们只能使用 FindControl("controlID") 模式来访问 Web 表控件内放置的 Web 控件。不过,对于动态变化的表(表的行数或列数基于某个数据库或用户指定的标准),我会使用 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 ;放置 “Add Products from Shipment” 和 “Cancel” 按钮的“页脚”行对应的是 BatchInsertFooterRow ;那些包含产品和单价的文本框控件的行对应的是 BatchInsertRow 和 BatchInsertAlternatingRow 值。我已在 Styles.css 文件中创建了相应的 CSS 类,使插入界面的外观类似于我们在以往教程中使用的 GridView 和 DetailsView 控件。这些 CSS 类如下所示。

/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/ 
.BatchInsertLabel 

    font-weight: bold; 
    text-align: right; 

 
.BatchInsertHeaderRow td 

    color: White; 
    #900; 
    padding: 11px; 

 
.BatchInsertFooterRow td 

    text-align: center; 
    padding-top: 5px; 

 
.BatchInsertRow 


 
.BatchInsertAlternatingRow 

    #fcc; 
}

输入这些标记后 , 返回Design 视图。该 <table> 应在设计器中显示为一个 4 列 7 行的表 , 如图 6 所示。

图6 : 插入界面由一个 4 列 7 行的表构成

现在 , 我们准备在插入界面中添加 Web 控件。从工具箱中将两个下拉列表拖放到表中相应的单元格里 – 一个用于显示供应商,一个用于显示类别。

将供应商下拉列表的 ID 属性设为 Suppliers , 并将它绑定到一个名为SuppliersDataSource 的新 ObjectDataSource 。配置新的 ObjectDataSource 从 SuppliersBLL 类的 GetSuppliers 方法获取数据,并将 UPDATE 选项卡的下拉列表设置为 “(None)” 。单击Finish 完成向导。

图7 : 配置 ObjectDataSource 使用 SuppliersBLL 类的 GetSuppliers 方法

设置Suppliers 下拉列表显示 CompanyName 数据字段 , 将SupplierID 数据字段作为其 ListItems 的值。

图8 : 显示 CompanyName 数据字段并使用 SupplierID 作为值

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

添加完这两个下拉列表并将它们绑定到进行了相应配置的 ObjectDataSource 后,你的屏幕应如图 9 所示。

图9 :“ 标题 ” 行现在包含Suppliers 和 Categories 下拉列表

现在 , 我们需要创建收集每种新产品名称和价格的文本框。对于产品名称和价格行中的每一行,从工具箱中将一个文本框控件拖放到设计器中。分别将这些文本框的 ID 属性设为 ProductName1 、 UnitPrice1 、 ProductName2 、 UnitPrice2 、 ProductName3 、 UnitPrice3 ,等等。

在每个单价文本框的后面添加一个 CompareValidator , 将其 ControlToValidate 属性设为适当的 ID 。同时,将 Operator 属性设为 GreaterThanEqual , ValueToCompare 属性设为 “0” , Type 属性设为 Currency 。这些设置指示 CompareValidator 确保输入的价格为大于或等于 0 的有效货币值。将 Text 属性设置为 “*” , ErrorMessage 属性设为 “The price must be greater than or equal to zero.Also, please omit any currency symbols.” 。

注意 : 插入界面不包含任何 RequiredFieldValidator 控件 , 即使 Products 数据库表中的ProductName 字段不允许 NULL 值。这是因为,我们想要让用户最多输入 5 种产品。例如,如果用户在前三行中提供产品名称和单价,而后两行为空,我们仅将三种新产品添加到系统。由于 ProductName 是必填字段,我们需要通过编码方式检查并确保在输入单价的同时也提供了相应的产品名称值。我们将在步骤 4 中进行该检查。

在确认用户的输入时,如果输入值包含货币符号, CompareValidator 将报告无效数据。在每个单价文本框的前面增加一个 “$” ,提示用户在输入价格的时候省略货币符号。

最后 , 在InsertingInterface 面板中增加一个ValidationSummary 控件 , 将其 ShowMessageBox 属性设为True ,ShowSummary 属性设为 False 。完成这些设置后,如果用户输入一个无效的单价值,在该文本框控件旁边会出现一个星号,且 ValidationSummary 将显示一个客户端消息框,显示我们在前面指定的错误消息。

此时 , 你的屏幕应如图 10 所示 。

图10 : 插入界面现在包含产品名称和价格的文本框

接下来 , 我们要在 “ 页脚 ” 行增加“Add Products from Shipment” 和 “Cancel” 按钮。从工具箱中将两个按钮控件拖放到插入界面的底部 , 分别将这两个按钮的 ID 属性设为 AddProducts 和CancelButton ,Text 属性分别设为 “Add Products from Shipment” 和 “Cancel” 。此外,将 CancelButton 控件的 CausesValidation 属性设为 false 。

最后,我们需要添加一个 Web 标签控件来显示这两个界面的状态消息。例如,当用户成功添加了一批新产品时,我们希望返回到显示界面并显示一条确认消息。当然,如果用户只提供了新产品的价格而没有提供产品名称,我们就需要显示一条警告消息,因为 ProductName 字段是必填字段。由于两个界面都需要显示这条消息,我们将它放在页面的顶部、面板控件的外面。

在设计器中,从工具箱中将一个 Web 标签控件拖放到页面的顶部。将 ID 属性设置为 StatusLabel ,清除 Text 属性,并将 Visible 和 EnableViewState 属性设为 false 。正如我们在前面教程中讨论的那样,将 EnableViewState 属性设为false 时,我们可以通过编码改变标签控件的属性,使它们在页面回传时自动恢复默认值。这使代码(实现状态消息在响应用户操作时显示,而在页面回传时消失)得到了简化。最后,将 StatusLabel 控件的 CssClass 属性设为 “Warning” ,这也是我们在 Styles.css 中定义的 CSS 类的名称,该类用大号、倾斜、加粗的红色字体显示文本。

图11 显示的是添加并配置了标签控件后的 Visual Studio 设计器。

图11 : 在两个面板控件的上方放置StatusLabel 控件

步骤3 : 在显示界面和插入界面之间切换

到此 , 我们已完成了显示界面和插入界面标记的编写 , 但我们还有两个任务要做 :

  • 在显示界面和插入界面之间切换
  • 将产品添加到数据库

目前 , 显示界面是可见的 , 而插入界面是隐藏的。这是因为 DisplayInterface 面板的 Visible 属性设为 true (默认值),而 InsertingInterface 面板的 Visible 属性为 false 。要在两个界面之间切换,我们只需要切换两个控件的 Visible 属性值。

单击“Process Product Shipment” 按钮时 , 我们想从显示界面切换到插入界面。因此 , 我们应该为该按钮的 Click 事件创建一个 Event Handler , 在其中包含以下代码 :

protected void ProcessShipment_Click(object sender, EventArgs e) 

    DisplayInterface.Visible = false; 
    InsertingInterface.Visible = true; 
}

上述代码的作用是隐藏 DisplayInterface 面板 , 并显示 InsertingInterface 面板。

然后 , 为插入界面中的 “Add Products from Shipment” 和“Cancel” 按钮控件创建 Event Handler 。单击其中任何一个按钮时,我们需要切回到显示界面。为这两个按钮控件的 Click 事件创建 Event Handler ,调用我们马上要添加的 ReturnToDisplayInterface 方法。除了隐藏 InsertingInterface 面板并显示 DisplayInterface 面板外, ReturnToDisplayInterface 方法还需要将 Web 控件返回到编辑前的状态。即,需要将下拉列表的 SelectedIndex 属性设置为 0 ,并清除文本框控件的 Text 属性。

注意 : 考虑一下 , 在返回显示界面之前 , 如果我们没有将这些控件返回到编辑前的状态 , 会发生什么事情呢 ? 一位用户可能单击 “Process Product Shipment” 按钮 , 输入产品 , 然后单击 “Add Products from Shipment” 。这将添加产品并返回到显示界面。此时,用户可能想添加另一批产品。单击 “Process Product Shipment” 按钮后,返回到插入界面,但下拉列表中的选择和文本框的值依然是用户上一次添加的值。

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 事件的 Event Handler 都只是调用ReturnToDisplayInterface 方法 , 但我们将在步骤 4 中回到 “Add Products from Shipment” Click 事件的 Event Handler 并添加保存产品的代码。 ReturnToDisplayInterface 先使 Suppliers 和 Categories 下拉列表返回到第一个选项。两个常量 firstControlID 和 lastControlID 用于标识在插入界面中标明产品名称和单价文本框时使用的起始和结束索引值,在一个 for 循环中用来将文本框控件的 Text 属性设置为空字符串。最后,重新设置面板的 Visible 属性,以隐藏插入界面并展示显示界面。

花些时间在浏览器中测试该页面。首次访问该页面时 , 用户看到的显示界面应如图 15 所示。单击 “Process Product Shipment” 按钮。之后页面回传 , 我们会看到插入界面 , 如图12 所示。单击 “Add Products from Shipment” 或 “Cancel” 按钮,返回到显示界面。

注意 : 在查看插入界面时,花些时间测试一下单价文本框上的 CompareValidator 。输入无效货币值或小于 0 的价格时,若单击 “Add Products from Shipment” 按钮,我们会看到一条客户端警告消息。

图12 : 单击 “Process Product Shipment” 按钮后显示插入界面

步骤4 : 添加产品

在本教程中 , 剩下要做的事情是在 “Add Products from Shipment” 按钮的Click 事件的 Event Handler 中实现将产品保存到数据库。为此,我们可以创建一个 ProductsDataTable ,并为每个产品名称增加一个 ProductsRow 实例。一旦增加完这些 ProductsRow ,我们会调用 ProductsBLL 类的 UpdateWithTransaction 方法,该方法将传递 ProductsDataTable 。回想一下我们在 在事务中封装数据库修改 教程中创建的 UpdateWithTransaction 方法,该方法将 ProductsDataTable 传递给 ProductsTableAdapter 的 UpdateWithTransaction 方法。此时,启动一个 ADO.NET 事务, TableAdatper 针对 DataTable 中增加的每个 ProductsRow 向数据库发出一个 INSERT 语句。如果所有的产品都增加无误,则提交事务,否则回滚事务。

“Add Products from Shipment” 按钮 Click 事件的 Event Handler 代码也需要执行一些错误检查。由于插入界面没有使用RequiredFieldValidator , 用户可以输入产品的价格而忽略其名称。由于产品的名称是必需的,如果出现这种情况,我们需要提醒用户并中断插入操作。完整的 Click 事件的 Event Handler 代码如下所示:

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

该Event Handler 首先确定 Page.IsValid 属性返回的值是否为 true 。如果返回 false ,那就表示至少有一个 CompareValidator 发现了无效数据;此时,我们要么停止插入输入的产品,要么在将用户输入的单价值赋值给 ProductsRow 的 UnitPrice 属性时,以抛出一个异常告终。

接下来 , 创建一个新的ProductsDataTable 实例 (products) 。在一个 for 循环中循环访问产品名称和单价文本框 , 并将 Text 属性读入到局部变量 productName 和 unitPrice 中。如果用户输入了产品的单价值,但没有输入相应的产品名称, StatusLabel 就显示消息 “If you provide a unit price you must also include the name of the product” ,并退出 Event Handler 。

如果提供了产品名称 , 则使用ProductsDataTable 的 NewProductsRow 方法创建一个新的 ProductsRow 实例。这个新 ProductsRow 实例的 ProductName 属性被设置为当前产品名称文本框中的值 , 而 SupplierID 和 CategoryID 属性被赋值为插入界面顶部的下拉列表中的SelectedValue 属性值。如果用户输入了产品的价格,就将该值赋值给 ProductsRow 实例的 UnitPrice 属性;否则,不对该属性赋值,这会导致数据库中该 UnitPrice 的值为 NULL 。最后, Discontinued 和 UnitsOnOrder 属性分别被赋值为固定值 false 和 0 。

完成对 ProductsRow 实例的属性赋值后,将其增加到 ProductsDataTable 。

完成 for 循环后,我们检查是否添加了新产品。用户有可能在输入产品名称或价格之前就单击了 “Add Products from Shipment” 按钮。如果 ProductsDataTable 中至少有一个产品,则调用 ProductsBLL 类的 UpdateWithTransaction 方法。接下来,重新将数据绑定到 ProductsGrid GridView ,这样,最近添加的产品将展示在显示界面中。更新 StatusLabel 来显示一条确认消息,然后调用 ReturnToDisplayInterface ,隐藏插入界面并展示显示界面。

如果没有输入任何产品 , 则仍然显示插入界面 , 不过会显示消息 “No products were added.Please enter the product names and unit prices in the textboxes” 。

图 13 、 14 和 15 显示的是活动中的插入界面和显示界面。在图 13 中,用户输入了单价,但没有输入相应的产品名称。图 14 显示的是成功添加三件新产品后的显示界面,而图 15 显示的是 GridView 中刚添加的两件新产品(第三件新产品在上一页中)。

图13 : 输入单价时 , 产品名称是必需的

图14 : 添加了供应商 Mayumi 提供的三种蔬菜产品

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

注意 : 本教程使用的批量插入逻辑将插入操作封装在一个事务中。为验证这一点,我们可有意引入一个数据库级的错误。例如 , 不将新ProductsRow 实例的 CategoryID 属性赋值为Categories 下拉列表中的选择值 , 而是赋值为 i * 5 。这里的 i 是介于 1 到 5 之间的循环索引值。因此,利用批量插入添加两件或更多产品时,第一件产品的 CategoryID 值 (5) 是有效的,但后面产品的 CategoryID 值与 Categories 表中的 CategoryID 值不匹配。实际结果是第一个 INSERT 成功,后面的插入操作因为外键约束违例而失败。由于批量插入是原子操作,所以第一个 INSERT 会回滚,数据库会回到批量插入开始之前的状态。

小结

在本教程以及前面两个教程中,我们创建了允许更新、删除和插入批量数据的界面,它们都用到了事务。事务是我们在 在事务中封装数据库修改 教程中向数据访问层添加的。在某些情况下,这些批处理用户界面极大提高了终端用户的效率:减少了鼠标单击次数、回传次数以及从键盘到鼠标的转换次数,同时还保证了基础数据的完整性。

对处理批量数据的探讨到此为止。在接下来的系列教程中,我们将探讨多种高级的数据访问层情况,包括在 TableAdapter 的方法中使用存储过程、在 DAL 中配置连接级和命令级的设置、对连接字符串进行加密等等。

快乐编程!

posted @ 2016-05-02 00:31  迅捷之风  阅读(205)  评论(0编辑  收藏  举报