批次插入 (C#)

由 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 控制項中,該控制項充當其他控制項的容器。 因此,我們的頁面將有兩個面板控制項,每個介面一個。

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

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

圖 2:將面板從工具箱拖到設計器上 (點選以查看完整大小的圖像)

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

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

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

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

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

完成 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 面板內,因此我們可以將面板的 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 確實有工具能透過 Designer 新增 <table> 元素,但 Designer 似乎總是在標記中注入不需要的 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 控制項,我們會很快地新增這些控制項。 請注意,每個 `` 元素都包含一個特定的 CSS 類別設定:`BatchInsertHeaderRow` 表示供應商和類別下拉選單將進入的表頭列;`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;
    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 以使用 類別的 方法 (按此檢視完整圖片)

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

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

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

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

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

標題列現在包含供應商和分類的下拉選單

圖 9:標題列現在包含SuppliersCategories下拉清單(點擊查看完整圖片

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

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

注意

即使 ProductName 資料庫表中的 Products 欄位不允許 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 欄位為必填。 由於我們需要在兩個介面上顯示此訊息,因此請將其放置在面板之外的頁面頂部。

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

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

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

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

第三步:切換顯示介面和插入介面

至此,我們已經完成了顯示和插入介面的標記,但我們仍然有兩個任務:

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

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

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

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

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

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

注意

考慮一下如果我們在返回顯示介面之前沒有將控制項返回到預編輯狀態,可能會發生什麼情況。 使用者可以點選「處理產品貨運」按鈕,輸入出貨中的產品,然後點選「從貨物中新增產品」。 這將新增產品並將使用者返回到顯示介面。 此時,使用者可能想要再增加一批貨件。 點擊「處理產品貨運」按鈕後,他們將返回到插入介面,但下拉式選單的選項和文字框的值仍會保持其先前的內容。

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 下拉式清單恢復到它們的第一個選項。 這兩個常數 firstControlIDlastControlID 標記了在插入介面中命名產品名稱和單位價格文字框時使用的起始和結束控制索引值,並且用於設定將 TextBox 控件的 Text 屬性重置為空字串的 for 循環的邊界。 最後重置面板的 Visible 屬性,以隱藏插入界面並顯示介面。

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

注意

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

點擊「處理產品出貨」按鈕後,顯示插入界面

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

步驟 4:新增產品

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

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

接下來,建立一個新 ProductsDataTable 執行個體 (products)。 使用 for 迴圈來逐一讀取產品名稱和單價的文本框,將 Text 屬性讀入本機變數 productNameunitPrice。 ** 如果使用者為單價輸入了值,但未提供相應的產品名稱,則狀態標籤顯示「如果提供單價,必須包含產品名稱」的訊息,並退出事件處理程序。

如果已提供產品名稱,則使用 ProductsDataTableNewProductsRow 方法建立新的 ProductsRow 執行個體。 這個新的 ProductsRow 執行個體的 ProductName 屬性被設定為目前產品名稱的 TextBox,而 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 中的連接和命令級設定、加密連接字串等!

快樂程式!

關於作者

斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 Scott 是一位獨立顧問、培訓師兼作家。 他的最新著作是《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以透過 mitchell@4GuysFromRolla.com 聯絡他。

特別感謝

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