批次插入 (C#)
演講者:Scott Mitchell
了解如何在單一操作中插入多個資料庫記錄。 在使用者介面層中,我們擴展了 GridView 以允許使用者輸入多個新記錄。 在資料存取層中,我們將多個插入操作包裹在一個交易中,以確保所有插入成功,或者在任何一個插入失敗時回滾所有插入。
簡介
在「批次更新」教學課程中,我們研究如何自訂 GridView 控制項以呈現可編輯多個記錄的介面。 造訪該頁面的使用者可以進行一系列更改,然後點擊一個按鈕即可執行批次更新。 對於使用者常常一次性更新多筆記錄的情況,這種介面可以節省無數次的點擊和鍵盤與滑鼠之間的切換,與最初在「插入、更新和刪除資料概述」教學課程中探討的逐列編輯功能相比,更加有效率。
新增記錄時也可以套用此概念。 想像一下,在 Northwind Traders,我們通常會收到供應商寄來的貨物,其中包含特定類別的多種產品。 例如,我們可能會從 Tokyo Traders 收到一批六種不同的茶和咖啡產品。 如果使用者透過 DetailsView 控制項一次輸入六個產品,他們將不得不一次又一次地選擇許多相同的值:例如,選擇相同的類別 (飲料)、相同的供應商 (東京貿易商)、相同的停產值 (False) 以及相同的訂貨單位數 (0)。 這種重複的資料輸入不僅耗時,而且容易出錯。
經過一些努力,我們可以建立一個批次插入介面,使得使用者可以一次選擇供應商和類別,輸入一系列產品名稱和單位價格,然後點擊一個按鈕將新產品新增到資料庫中 (見圖 1)。 每當新增一個產品時,其 ProductName
和 UnitPrice
資料欄位會被賦予從文字輸入框中輸入的值,而 CategoryID
和 SupplierID
的值則會從表單頂部的下拉式清單中獲取。 Discontinued
和 UnitsOnOrder
值分別設定為 false
和 0 的硬式編碼值。
圖1:批次插入介面 (點擊查看完整圖片)
在本教學課程中,我們將建立一個頁面來實現如圖 1 所示的批次插入介面。 與前兩個教學課程一樣,我們將把插入包裝在交易範圍內以確保原子性。 讓我們開始吧!
步驟 1:建立顯示介面
本教學課程將由一個頁面組成,該頁面分為兩個區域:顯示區域和插入區域。 我們將在此步驟中建立的顯示介面在 GridView 中顯示產品,並包含一個標題為「處理產品貨運」的按鈕。 點選該按鈕後,顯示介面變為插入介面,如圖 1 所示。 點選「從貨物中新增產品」或「取消」按鈕後返回顯示介面。 我們將在步驟 2 中建立插入介面。
在建立具有兩個介面 (一次僅其中一個介面) 的頁面時,每個介面通常會放置在一個面板 Web 控制項中,該控制項會充當其他控制項的容器。 因此,我們的頁面將有兩個面板控制項,每個介面一個。
先開啟 BatchData
資料夾中的 BatchInsert.aspx
頁面,然後將面板從工具箱拖曳到設計器上 (請參閱圖 2)。 將面板的 ID
屬性設為 DisplayInterface
。 將面板新增至設計器時,其 Height
和 Width
屬性分別設定為 50px 和 125px。 從「屬性」視窗中清除這些屬性值。
圖 2:將面板從工具箱拖曳到設計器上 (點擊查看完整圖片)
接下來,將 Button 和 GridView 控制項拖曳到面板中。 將 Button 的 ID
屬性設為 ProcessShipment
,並將其 Text
屬性設為「處理產品貨運」。 將 GridView 的 ID
屬性設為 ProductsGrid
,並從其智慧標籤將其繫結到名為 ProductsDataSource
的新 ObjectDataSource。 設定 ObjectDataSource 以從 ProductsBLL
類別的 GetProducts
方法中提取其資料。 由於此 GridView 僅用於顯示資料,因此請將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無)。 按一下「完成」以完成「設定資料來源」精靈。
圖 3:顯示從 ProductsBLL
類別的GetProducts
方法傳回的資料 (點擊查看完整圖片)
圖 4:將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無) (點擊查看完整圖片)
完成 ObjectDataSource 精靈後,Visual Studio 將為產品資料欄位新增 BoundFields 和 CheckBoxField。 移除 ProductName
、CategoryName
、SupplierName
、UnitPrice
和 Discontinued
欄位之外的所有欄位。 請按照自己的審美隨意進行自訂。 我決定將 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 上方看到一個「處理產品貨運」按鈕,該按鈕一次列出十個產品。
圖 5:GridView 列出產品並提供排序和分頁功能 (點擊查看完整圖片)
步驟 2:建立插入介面
顯示介面完成後,我們就可以建立插入介面了。 在本教學課程中,我們將建立一個插入介面,提示輸入單一供應商和類別值,然後允許使用者輸入最多五個產品名稱和單一價值。 透過此介面,使用者可以新增一到五個新產品,這些產品都具有相同的類別和供應商,但具有唯一的產品名稱和價格。
首先,將面板從工具箱拖曳到設計器上,將其放置在現有 DisplayInterface
面板下方。 將這個剛新增面板的 ID
屬性設為 InsertingInterface
,並將其 Visible
屬性設為 false
。 我們將在步驟 3 中新增將 InsertingInterface
面板的 Visible
屬性設為 true
的程式碼。 也要清除面板的 Height
和 Width
屬性值。
接下來,我們需要建立如圖 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
用於頁腳列,「從貨物中新增產品」和「取消」按鈕將位於該列;並為將包含產品和單價文字輸入框控制項的行交替使用 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 標籤的下拉式清單設為 (無)。 按一下 [完成] 以完成程序。
圖 7:設定 ObjectDataSource 以使用 SuppliersBLL
類別的 GetSuppliers
方法 (點擊查看完整圖片)
讓 Suppliers
下拉式清單顯示 CompanyName
資料欄位,並使用 SupplierID
資料欄位作為其 ListItem
的值。
圖 8:顯示 CompanyName
資料欄位,並使用 SupplierID
作為值 (點擊查看完整圖片)
命名第二個下拉式清單 Categories
,並將其繫結到名為 CategoriesDataSource
的新 ObjectDataSource。 設定 CategoriesDataSource
ObjectDataSource 以使用 CategoriesBLL
類別的 GetCategories
方法;將「更新」和「刪除」標籤中的下拉式清單設為 (無),然後按一下「完成」以完成精靈。 最後,讓下拉式清單顯示 CategoryName
資料欄位並使用 CategoryID
作為值。
新增這兩個下拉式清單,並將其繫結到適當設定的 ObjectDataSource 後,您的畫面應類似於圖 9。
圖 9:標題列現在包含 Suppliers
和 Categories
(點擊查看完整圖片)
我們現在需要建立文字輸入框來收集每個新產品的名稱和價格。 將 TextBox 控制項從工具箱拖曳到設計器上,為五個產品名稱和價格行中的每一列。 將文字輸入框的 ID
屬性設為 ProductName1
、UnitPrice1
、ProductName2
、UnitPrice2
、ProductName3
、UnitPrice3
等。
在每個單價文字輸入框後面加上一個 CompareValidator,將 ControlToValidate
屬性設為適當的 ID
。 另外,將 Operator
屬性設為 GreaterThanEqual
、ValueToCompare
設為 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
屬性分別設為 AddProducts
和 CancelButton
,並將 Text
屬性分別設為「從貨物中新增產品」和「取消」。 另外,將 CancelButton
控制項的 CausesValidation
屬性設為 false
。
最後,我們需要新增一個 Label Web 控制項,用於顯示兩個介面的狀態訊息。 例如,當使用者成功新增出貨的產品時,我們希望返回顯示介面並顯示確認訊息。 但是,如果使用者提供新產品的價格但省略了產品名稱,則我們需要顯示警告訊息,因為 ProductName
欄位是必要的。 由於我們需要在兩個介面上顯示此訊息,因此請將其放置在面板之外的頁面頂部。
將標籤 Web 控制項從工具箱拖曳到設計器中的頁面頂部。 將 ID
屬性設為 StatusLabel
,清除 Text
屬性,然後將 Visible
和 EnableViewState
屬性設為 false
。 正如我們在先前的教學課程中所看到的,將 EnableViewState
屬性設為 false
可讓我們以編程方式更改 Label 的屬性值,並讓它們在後續回傳時自動恢復為預設值。 這簡化了用於顯示狀態訊息以回應某些使用者操作的程式碼,該狀態訊息在後續回傳時消失。 最後,將 StatusLabel
控制項的 CssClass
屬性設為「警告」,這是定義在 Styles.css
中的一個 CSS 類別,用於以大號、斜體、粗體、紅色字體顯示文字。
圖 11 顯示了新增並設定標籤後的 Visual Studio 設計器。
圖 11:將 StatusLabel
控制項放置在兩個面板控制項上方 (點擊查看完整圖片)
第三步:切換顯示介面和插入介面
至此,我們已經完成了顯示和插入介面的標記,但我們仍然有兩個任務:
- 顯示介面和插入介面切換
- 將貨件中的產品加入資料庫
目前顯示介面可見,插入介面隱藏。 這是因為 DisplayInterface
面板的 Visible
屬性設定為 true
(預設值),而 InsertingInterface
面板的 Visible
屬性則設定為 false
。 要在兩個介面之間切換,我們只需切換每個控制項的 Visible
屬性值。
我們希望在按一下「處理產品貨運」按鈕時從顯示介面移動到插入介面。 因此,為此按鈕的 Click
事件建立一個事件處理常式,其中包含以下程式碼:
protected void ProcessShipment_Click(object sender, EventArgs e)
{
DisplayInterface.Visible = false;
InsertingInterface.Visible = true;
}
此程式碼只是隱藏 DisplayInterface
面板並顯示 InsertingInterface
面板。
接下來,為插入介面中的「從貨物中新增產品」和「取消按鈕」控制項建立事件處理常式。 當點擊這兩個按鈕中的任何一個時,我們需要返回顯示介面。 為兩個按鈕控制項建立 Click
事件處理常式,使它們都呼叫 ReturnToDisplayInterface
方法,這是我們稍後會新增的方法。 除了隱藏 InsertingInterface
面板和顯示 DisplayInterface
面板之外,ReturnToDisplayInterface
方法還需要將 Web 控制項傳回其預先編輯狀態。 這涉及將下拉式清單 SelectedIndex
屬性設為 0,清除 TextBox 控制項的 Text
屬性。
注意
考慮一下如果我們在返回顯示介面之前沒有將控制項返回到預編輯狀態,可能會發生什麼情況。 使用者可以點選「處理產品貨運」按鈕,輸入出貨中的產品,然後點選「從貨物中新增產品」。 這將新增產品並將使用者返回到顯示介面。 此時,使用者可能想要再增加一批貨件。 點擊「處理產品貨運」按鈕後,他們將返回插入介面,但下拉式清單選項和 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;
}
儘管我們將返回步驟 4 中的「從貨物中新增產品」Click
事件處理常式並新增程式碼來保存產品,但這兩個 Click
事件處理常式都只是呼叫 ReturnToDisplayInterface
方法。 ReturnToDisplayInterface
會先將 Suppliers
和 Categories
下拉式清單恢復到它們的第一個選項。 這兩個常數 firstControlID
和 lastControlID
標記了在插入介面中命名產品名稱和單位價格文字輸入框時使用的起始和結束控制索引值,並且用於設定將 Text
屬性重置為空字串的 for
循環的邊界。 最後重置面板的 Visible
屬性,以隱藏插入介面並顯示顯示介面。
花點時間在瀏覽器中測試此頁面。 第一次造訪該頁面時,您應該會看到如圖 5 所示的顯示介面。 點選「處理產品貨運」按鈕。 該頁面將回傳,您現在應該看到插入介面,如圖 12 所示。 按一下「從貨物中新增產品」或「取消」按鈕將返回顯示介面。
注意
查看插入介面時,花點時間測試單價文字輸入框上的 CompareValidators。 當您按一下「從貨物中新增產品」按鈕時,如果貨幣值無效或價格值小於零,您應該會看到用戶端訊息方塊警告。
圖12:點選「處理產品貨運」按鈕後顯示插入介面 (點擊查看完整圖片)
步驟 4:新增產品
本教學課程剩下的工作就是將產品儲存到「從貨物中新增產品」Click
事件處理常式中的資料庫。 這可以透過建立一個 ProductsDataTable
,並為每個提供的產品名稱新增一個 ProductsRow
執行個體來實現。 新增這些 ProductsRow
後,我們將呼叫 ProductsBLL
類別的 UpdateWithTransaction
方法,並將 ProductsDataTable
作為參數傳入。 回想一下,UpdateWithTransaction
方法是在「在交易中包裝資料庫修改」教學課程中建立的,它將 ProductsDataTable
傳遞給 ProductsTableAdapter
的 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 報告了無效資料;在這種情況下,我們不想嘗試插入輸入的產品,否則在嘗試將使用者輸入的單位價格值賦值給 ProductsRow
的 UnitPrice
屬性時,將會引發例外狀況。
接下來,建立一個新 ProductsDataTable
執行個體 (products
)。 使用 for
循環來迭代產品名稱和單價的文字輸入框,並將 Text
屬性讀取到本機變數 productName
和 unitPrice
。 如果使用者為單位價格輸入了值但未提供相應的產品名稱,則 StatusLabel
顯示訊息「如果提供單位價格,必須包含產品名稱」,並退出事件處理常式。
如果已提供產品名稱,則使用 ProductsDataTable
的 NewProductsRow
方法建立新的 ProductsRow
執行個體。 這個新 ProductsRow
執行個體的 ProductName
屬性設定為目前產品名稱文字輸入框的值,而 SupplierID
和 CategoryID
屬性則分別被賦值為插入介面標題中的下拉式清單的 SelectedValue
屬性。 如果使用者為產品的價格輸入了值,則該值會被賦值給 ProductsRow
執行個體的 UnitPrice
屬性;否則,該屬性將保持未賦值,這將導致資料庫中 UnitPrice
的值為 NULL
。 最後,Discontinued
和 UnitsOnOrder
屬性分別指派給硬式編碼值 false
和 0。
將屬性指派給 ProductsRow
執行個體後,會將其新增至 ProductsDataTable
。
for
循環完成後,我們檢查是否新增了任何產品。 畢竟,使用者可能在輸入任何產品名稱或價格之前已經按一下了「從發貨中新增產品」。 如果 ProductsDataTable
中至少有一種產品,則呼叫 ProductsBLL
類別的 UpdateWithTransaction
方法。 接下來,資料會重新繫結到 ProductsGrid
GridView,以便新新增的產品能夠顯示在顯示介面中。 StatusLabel
會更新以顯示確認訊息,並叫用 ReturnToDisplayInterface
,隱藏插入介面並顯示顯示介面。
如果沒有輸入產品,則仍顯示插入介面,但提示「未新增產品」。 請在顯示的文字輸入框中輸入產品名稱和單價。
圖 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 中的連接和命令級設定、加密連接字串等!
快樂程式!
關於作者
Scott Mitchell 是七本 ASP/ASP.NET 書籍的作者,也是 4GuysFromRolla.com 的創辦人,自 1998 年以來一直在使用 Microsoft 網路技術。 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。