共用方式為


批次插入 (VB)

作者:Scott Mitchell

下載 PDF

了解如何在單一操作中插入多個資料庫記錄。 在使用者介面層中,我們擴展了 GridView 以允許使用者輸入多個新記錄。 在資料存取層中,我們將多個插入操作包裹在一個交易中,以確保所有插入成功,或者在任何一個插入失敗時回滾所有插入。

簡介

在「批次更新」教學課程中,我們研究如何自訂 GridView 控制項以呈現可編輯多個記錄的介面。 造訪該頁面的使用者可以進行一系列更改,然後點擊一個按鈕即可執行批次更新。 對於使用者常常一次性更新多筆記錄的情況,這種介面可以節省無數次的點擊和鍵盤與滑鼠之間的切換,與最初在「插入、更新和刪除資料概述」教學課程中探討的逐列編輯功能相比,更加有效率。

新增記錄時也可以套用此概念。 想像一下,在 Northwind Traders,我們通常會收到供應商寄來的貨物,其中包含特定類別的多種產品。 例如,我們可能會從 Tokyo Traders 收到一批六種不同的茶和咖啡產品。 如果使用者透過 DetailsView 控制項一次輸入六個產品,他們將不得不一次又一次地選擇許多相同的值:例如,選擇相同的類別 (飲料)、相同的供應商 (東京貿易商)、相同的停產值 (False) 以及相同的訂貨單位數 (0)。 這種重複的資料輸入不僅耗時,而且容易出錯。

經過一些努力,我們可以建立一個批次插入介面,使得使用者可以一次選擇供應商和類別,輸入一系列產品名稱和單位價格,然後點擊一個按鈕將新產品新增到資料庫中 (見圖 1)。 每當新增一個產品時,其 ProductNameUnitPrice 資料欄位會被賦予從文字輸入框中輸入的值,而 CategoryIDSupplierID 的值則會從表單頂部的下拉式清單中獲取。 DiscontinuedUnitsOnOrder 值分別設定為 False 和 0 的硬式編碼值。

批次插入介面

圖1:批次插入介面 (點擊查看完整圖片)

在本教學課程中,我們將建立一個頁面來實現如圖 1 所示的批次插入介面。 與前兩個教學課程一樣,我們將把插入包裝在交易範圍內以確保原子性。 現在就開始吧!

步驟 1:建立顯示介面

這個教學課程將由一個單頁面組成,該頁面分為兩個區域:顯示區域和插入區域。 我們將在此步驟中建立的顯示介面在 GridView 中顯示產品,並包含一個標題為「處理產品貨運」的按鈕。 點選該按鈕後,顯示介面會變為插入介面,如圖 1 所示。 點選「從貨運中新增產品」或「取消」按鈕後返回顯示介面。 我們將在步驟 2 中建立插入介面。

在建立具有兩個介面 (一次僅其中一個介面) 的頁面時,每個介面通常會放置在一個面板 Web 控制項中,該控制項會充當其他控制項的容器。 因此,我們的頁面將有兩個面板控制項,每個介面一個。

先開啟 BatchData 資料夾中的 BatchInsert.aspx 頁面,然後將面板從工具箱拖曳到設計器上 (請參閱圖 2)。 將面板的 ID 屬性設為 DisplayInterface。 將面板新增至設計器時,其 HeightWidth 屬性分別設定為 50px 和 125px。 從「屬性」視窗中清除這些屬性值。

將面板從工具箱拖曳到設計器上

圖 2:將面板從工具箱拖曳到設計器上 (點擊查看完整圖片)

接下來,將 Button 和 GridView 控制項拖曳到面板中。 將 Button 的 ID 屬性設為 ProcessShipment,並將其 Text 屬性設為「處理產品貨運」。 將 GridView 的 ID 屬性設為 ProductsGrid,並從其智慧標籤將其繫結到名為 ProductsDataSource 的新 ObjectDataSource。 設定 ObjectDataSource 以從 ProductsBLL 類別的 GetProducts 方法中提取其資料。 由於此 GridView 僅用於顯示資料,因此請將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無)。 按一下「完成」以完成「設定資料來源」精靈。

顯示從 ProductsBLL 類別的 GetProducts 方法傳回的資料

圖 3:顯示從 ProductsBLL 類別的GetProducts 方法傳回的資料 (點擊查看完整圖片)

將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無)

圖 4:將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無) (點擊查看完整圖片)

完成 ObjectDataSource 精靈後,Visual Studio 將為產品資料欄位新增 BoundFields 和 CheckBoxField。 移除 ProductNameCategoryNameSupplierNameUnitPriceDiscontinued 欄位之外的所有欄位。 請按照自己的審美隨意進行自訂。 我決定將 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> 標籤內。 由於這些控制項位於 DisplayInterface 面板內,因此我們只需將面板的 Visible 屬性設為 False 即可隱藏它們。 步驟 3 著眼於以程式方式更改面板的 Visible 屬性,以回應按一下按鈕以顯示一個介面並隱藏另一個介面。

花點時間透過瀏覽器查看我們的進度。 如圖 5 所示,您應該在 GridView 上方看到一個「處理產品貨運」按鈕,該按鈕一次列出十個產品。

GridView 會列出產品,並提供排序和分頁功能

圖 5:GridView 列出產品並提供排序和分頁功能 (點擊查看完整圖片)

步驟 2:建立插入介面

顯示介面完成後,我們就可以建立插入介面了。 在本教學課程中,我們將建立一個插入介面,提示輸入單一供應商和類別值,然後允許使用者輸入最多五個產品名稱和單一價值。 透過此介面,使用者可以新增一到五個新產品,這些產品都具有相同的類別和供應商,但具有唯一的產品名稱和價格。

首先,將面板從工具箱拖曳到設計器上,將其放置在現有 DisplayInterface 面板下方。 將這個剛新增面板的 ID 屬性設為 InsertingInterface,並將其 Visible 屬性設為 False。 我們將在步驟 3 中新增將 InsertingInterface 面板的 Visible 屬性設為 True 的程式碼。 也要清除面板的 HeightWidth 屬性值。

接下來,我們需要建立如圖 1 所示的插入介面。 這個介面可以透過多種 HTML 技術建立,但我們將使用相當簡單的技術:四列七行表。

注意

輸入 HTML <table> 元素的標記時,我更喜歡使用「來源」檢視。 雖然 Visual Studio 確實具有透過設計器新增 <table> 元素的工具,但設計器似乎總是會在標記中注入不需要的 style 設定。 建立 <table> 標記後,我通常會傳回設計器來新增 Web 控制項並設定它們的屬性。 在建立具有預先確定的列和欄的資料表時,我喜歡使用靜態 HTML 而不是Table Web 控制項,因為放置在 Table Web 控制項中的任何 Web 控制項只能使用 FindControl("controlID") 模式進行存取。 不過,我確實會使用 Table Web 控制項來處理動態大小的資料表 (其列或欄基於某些資料庫或使用者指定的標準),因為 Table 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 表示供應商和類別下拉式清單將進入的標題列;BatchInsertFooterRow 用於頁腳列,「從貨運中新增產品」和「取消」按鈕將位於該列;並為將包含產品和單價文字輸入框控制項的行交替使用 BatchInsertRowBatchInsertAlternatingRow 值。 我在 Styles.css 檔案中建立了相應的 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> 應在設計器中顯示為四列七行表,如圖 6 所示。

插入介面由一個四列七行的資料表組成

圖6:插入介面由一個四列七行的資料表組成 (點擊查看完整圖片)

我們現在準備將 Web 控制項新增至插入介面。 將兩個下拉式清單從工具箱拖曳到表中的對應儲存格中,一個用於供應商,一個用於類別。

將供應商下拉式清單的 ID 屬性設為 Suppliers,並將其繫結到名為 SuppliersDataSource 的新 ObjectDataSource。 設定新的 ObjectDataSource 以從 SuppliersBLL 類別的 GetSuppliers 方法檢索其資料,並將 UPDATE 標籤的下拉式清單設為 (無)。 按一下 [完成] 以完成程序。

設定 ObjectDataSource 以使用 SuppliersBLL 類別的 GetSuppliers 方法

圖 7:設定 ObjectDataSource 以使用 SuppliersBLL 類別的 GetSuppliers 方法 (點擊查看完整圖片)

Suppliers 下拉式清單顯示 CompanyName 資料欄位,並使用 SupplierID 資料欄位作為其 ListItem 的值。

顯示 CompanyName 資料欄位,並使用 SupplierID 作為值

圖 8:顯示 CompanyName 資料欄位,並使用 SupplierID 作為值 (點擊查看完整圖片)

命名第二個下拉式清單 Categories,並將其繫結到名為 CategoriesDataSource 的新 ObjectDataSource。 設定 CategoriesDataSource ObjectDataSource 以使用 CategoriesBLL 類別的 GetCategories 方法;將「更新」和「刪除」標籤中的下拉式清單設為 (無),然後按一下「完成」以完成精靈。 最後,讓下拉式清單顯示 CategoryName 資料欄位並使用 CategoryID 作為值。

新增這兩個下拉式清單,並將其繫結到適當設定的 ObjectDataSource 後,您的畫面應類似於圖 9。

標題列現在包含 Suppliers 和 Categories 下拉式清單

圖 9:標題列現在包含 SuppliersCategories (點擊查看完整圖片)

我們現在需要建立文字輸入框來收集每個新產品的名稱和價格。 將 TextBox 控制項從工具箱拖曳到設計器上,為五個產品名稱和價格行中的每一列。 將文字輸入框的 ID 屬性設為 ProductName1UnitPrice1ProductName2UnitPrice2ProductName3UnitPrice3 等。

在每個單價文字輸入框後面加上一個 CompareValidator,將 ControlToValidate 屬性設為適當的 ID。 另外,將 Operator 屬性設為 GreaterThanEqualValueToCompare 設為 0,Type 設為 Currency。 這些設定指示 CompareValidator 確保價格 (如果輸入) 是大於或等於零的有效貨幣值。 將 Text 屬性設為 *,並將 ErrorMessage 設為「價格必須大於或等於零」。 另外,請省略任何貨幣符號。

注意

即使 Products 資料庫表中的 ProductName 欄位不允許 NULL 值,插入介面也不包含任何 RequiredFieldValidator 控制項。 這是因為我們想讓使用者最多輸入五個產品。 例如,如果使用者要提供前三列的產品名稱和單價,將最後兩行留空,我們只需在系統中新增三個新產品。 然而,由於 ProductName 是必要的,我們需要以程式設計方式檢查以確保如果輸入單價,則提供相應的產品名稱值。 我們將在步驟 4 中處理此檢查。

驗證使用者的輸入時,如果值包含貨幣符號,CompareValidator 會報告無效資料。 在每個單價文字輸入框前面加上一個 $,作為視覺提示,指示使用者在輸入價格時省略貨幣符號。

最後,在 InsertingInterface 面板中新增一個 ValidationSummary 控制項,將其 ShowMessageBox 屬性設為 True,並將其 ShowSummary 屬性設為 False。 透過這些設定,如果使用者輸入無效的單價值,則有問題的 TextBox 控制項旁邊將出現一個星號,並且 ValidationSummary 將顯示一個用戶端訊息框,其中顯示我們先前指定的錯誤訊息。

此時,您的畫面看起來應該類似圖 10。

插入介面現在包括產品名稱和價格的文字輸入框

圖 10:插入介面現在包括產品名稱和價格的文字輸入框 (點擊查看完整圖片)

接下來,我們需要將「從貨運中新增產品」和「取消」按鈕新增到頁尾列。 將兩個 Button 控制項從工具箱拖曳到插入介面的頁腳中,將 Button 的 ID 屬性分別設為 AddProductsCancelButton,並將 Text 屬性分別設為「從貨運中新增產品」和「取消」。 另外,將 CancelButton 控制項的 CausesValidation 屬性設為 false

最後,我們需要新增一個 Label Web 控制項,用於顯示兩個介面的狀態訊息。 例如,當使用者成功新增出貨的產品時,我們希望傳回顯示介面並顯示確認訊息。 但是,如果使用者提供新產品的價格但省略了產品名稱,則我們需要顯示警告訊息,因為 ProductName 欄位是必要的。 由於我們需要在兩個介面上顯示此訊息,因此請將其放置在面板之外的頁面頂部。

將 Label Web 控制項從工具箱拖曳到設計器中的頁面頂部。 將 ID 屬性設為 StatusLabel,清除 Text 屬性,然後將 VisibleEnableViewState 屬性設為 False。 正如我們在先前的教學課程中所看到的,將 EnableViewState 屬性設為 False 可讓我們以編程方式更改 Label 的屬性值,並讓它們在後續回傳時自動恢復為預設值。 這簡化了用於顯示狀態訊息以回應某些使用者操作的程式碼,該狀態訊息在後續回傳時消失。 最後,將 StatusLabel 控制項的 CssClass 屬性設為「警告」,這是定義在 Styles.css 中的一個 CSS 類別,用於以大號、斜體、粗體、紅色字體顯示文字。

圖 11 顯示了新增和設定標籤後的 Visual Studio 設計器。

將 StatusLabel 控制項放置在兩個面板控制項上方

圖 11:將 StatusLabel 控制項放置在兩個面板控制項上方 (點擊查看完整圖片)

步驟 3:切換顯示介面和插入介面

至此,我們已經完成了顯示和插入介面的標記,但我們還有兩項工作要做:

  • 顯示介面和插入介面切換
  • 將貨物中的產品加入資料庫

目前顯示介面可見,但插入介面是隱藏的。 這是因為 DisplayInterface 面板的 Visible 屬性設定為 True (預設值),而 InsertingInterface 面板的 Visible 屬性則設定為 False。 要在兩個介面之間切換,我們只需切換每個控制項的 Visible 屬性值。

我們希望在按一下「處理產品貨運」按鈕時從顯示介面移動到插入介面。 因此,為此按鈕的 Click 事件建立一個事件處理常式,其中包含以下程式碼:

Protected Sub ProcessShipment_Click(sender As Object, e As EventArgs) _
    Handles ProcessShipment.Click
    DisplayInterface.Visible = False
    InsertingInterface.Visible = True
End Sub

此程式碼只是隱藏 DisplayInterface 面板並顯示 InsertingInterface 面板。

接下來,為插入介面中的「從貨運中新增產品」和「取消按鈕」控制項建立事件處理常式。 當點擊這兩個按鈕中的任何一個時,我們需要返回顯示介面。 為兩個按鈕控制項建立 Click 事件處理常式,使它們都呼叫 ReturnToDisplayInterface 方法,這是我們稍後會新增的方法。 除了隱藏 InsertingInterface 面板和顯示 DisplayInterface 面板之外,ReturnToDisplayInterface 方法還需要將 Web 控制項傳回其預先編輯狀態。 這涉及將下拉式清單 SelectedIndex 屬性設為 0,清除 TextBox 控制項的 Text 屬性。

注意

考慮一下如果我們在返回顯示介面之前沒有將控制項返回到預編輯狀態,可能會發生什麼情況。 使用者可以點選「處理產品貨運」按鈕,輸入出貨中的產品,然後點選「從貨運中新增產品」。 這將新增產品,並將使用者送回顯示介面。 此時,使用者可能想要再增加一批貨物。 點擊「處理產品貨運」按鈕後,他們將返回插入介面,但下拉式清單選項和 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

儘管我們將返回步驟 4 中的「從貨運中新增產品」Click 事件處理常式並新增程式碼來儲存產品,但這兩個 Click 事件處理常式都只是呼叫 ReturnToDisplayInterface 方法。 ReturnToDisplayInterface 會先將 SuppliersCategories 下拉式清單恢復到它們的第一個選項。 這兩個常數 firstControlIDlastControlID 標記了在插入介面中命名產品名稱和單位價格文字輸入框時使用的起始和結束控制索引值,並且用於設定將 Text 屬性重置為空字串的 For 循環的邊界。 最後重置面板的 Visible 屬性,以隱藏插入介面並顯示顯示介面。

花點時間在瀏覽器中測試此頁面。 第一次造訪該頁面時,您應該會看到如圖 5 所示的顯示介面。 點選「處理產品貨運」按鈕。 該頁面將回傳,您現在應該看到插入介面,如圖 12 所示。 按一下「從貨運中新增產品」或「取消」按鈕將返回顯示介面。

注意

查看插入介面時,花點時間測試單價文字輸入框上的 CompareValidators。 當您按一下「從貨運中新增產品」按鈕時,如果貨幣值無效或價格值小於零,您應該會看到用戶端訊息方塊警告。

點選「處理產品貨運」按鈕後顯示插入介面

圖12:點選「處理產品貨運」按鈕後顯示插入介面 (點擊查看完整圖片)

步驟 4:新增產品

本教學課程剩下的工作就是將產品儲存到「從貨運中新增產品」Click 事件處理常式中的資料庫。 這可以透過建立一個 ProductsDataTable,並為每個提供的產品名稱新增一個 ProductsRow 執行個體來實現。 新增這些 ProductsRow 後,我們將呼叫 ProductsBLL 類別的 UpdateWithTransaction 方法,並將 ProductsDataTable 作為參數傳入。 回想一下,UpdateWithTransaction 方法是在「在交易中包裝資料庫修改」教學課程中建立的,它將 ProductsDataTable 傳遞給 ProductsTableAdapterUpdateWithTransaction 方法。 接下來,啟動一個 ADO.NET 交易,然後 TableAdapter 對於 DataTable 中每個新增的 ProductsRow 向資料庫發出一個 INSERT 陳述式。 假設所有產品均已新增且沒有錯誤,則會提交交易,將進行回滾。

「從貨運中新增產品」按鈕的 Click 事件處理常式的程式碼還需要執行一些錯誤檢查。 由於在插入介面中沒有使用必填欄位驗證程式,使用者可以為產品輸入價格,但可以省略產品名稱。 由於產品名稱是必填項目,如果發生這種情況,我們需要提醒使用者,而不是繼續插入。 完整的 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,則表示一個或多個 CompareValidators 報告了無效資料;在這種情況下,我們不想嘗試插入輸入的產品,否則在嘗試將使用者輸入的單位價格值賦值給 ProductsRowUnitPrice 屬性時,將會引發例外狀況。

接下來,建立一個新 ProductsDataTable 執行個體 (products)。 使用 For 循環來迭代產品名稱和單價的文字輸入框,並將 Text 屬性讀取到本機變數 productNameunitPrice。 如果使用者為單位價格輸入了值但未提供相應的產品名稱,則 StatusLabel 顯示訊息「如果提供單位價格,必須包含產品名稱」,並退出事件處理常式。

如果已提供產品名稱,則使用 ProductsDataTableNewProductsRow 方法建立新的 ProductsRow 執行個體。 這個新 ProductsRow 執行個體的 ProductName 屬性設定為目前產品名稱文字輸入框的值,而 SupplierIDCategoryID 屬性則分別被賦值為插入介面標題中的下拉式清單的 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 增加了三種新蔬菜

圖 14:供應商 Mayumi 增加了三種新蔬菜 (點擊查看完整圖片)

在 GridView 的最後一頁可以找到新產品

圖 15:在 GridView 的最後一頁可以找到新產品 (點擊查看完整圖片)

注意

本教學課程中使用的批次插入邏輯將插入包裝在交易範圍內。 為了驗證這一點,故意引入一個資料庫層級的錯誤。 例如,不要將新 ProductsRow 執行個體的 CategoryID 屬性指派給 Categories 下拉式清單中的選取值,而是將其指派給類似 i * 5 的值。 以下 i 是循環索引子,其值範圍為 1 到 5。 因此,在批次插入中新增兩個或多個產品時,第一個產品將具有有效 CategoryID 值 (5),但後續產品的 CategoryID 值將與 Categories 表中的 CategoryID 值不符。 第一個 INSERT 會成功,但隨後的插入將因違反外部索引鍵限制而失敗。 由於批次插入是原子的,因此第一個 INSERT 將回滾,將資料庫返回到批次插入過程開始之前的狀態。

摘要

在本教學課程和前兩個教學課程中,我們建立了允許更新、刪除和插入批次資料的介面,所有這些都使用了我們在「在交易中包裝資料庫修改」教學課程中新增到資料存取層的交易支援。 對於某些場景,這種批次使用者介面透過減少點擊次數、回傳和鍵盤到滑鼠上下文切換的次數,大大提高了終端使用者的效率,同時也保持了底層資料的完整性。

這個教學課程總結了我們對批次資料操作的概念。 接下來的一系列教學課程將探索各種進階的資料存取層場景,包括在 TableAdapter 的方法中使用儲存程序、設定 DAL 中的連接和命令級設定、加密連接字串等!

祝您程式設計愉快!

關於作者

Scott Mitchell,七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 創始人,自 1998 年以來便開始使用 Microsoft Web 技術。 Scott 擔任獨立顧問、講師和作家。 他的新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以透過 mitchell@4GuysFromRolla.com 或他的部落格 (可以在 http://ScottOnWriting.NET 找到) 與他聯繫。

特別感謝

本教學課程系列已經過許多熱心的檢閱者檢閱。 本教學課程的主要審閱者是 Hilton Giesenow 和 S ren Jacob Lauritsen。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果有的話請寫信給我,信箱是 mitchell@4GuysFromRolla.com。