共用方式為


建立資料存取層 (C#)

由斯科特· 米切爾

下載 PDF

在本教學課程中,我們將從頭開始,並使用具類型的 DataSets 建立數據存取層 (DAL),以存取資料庫中的資訊。

簡介

作為 Web 開發人員,我們的生活圍繞使用數據。 我們會建立資料庫來儲存數據、擷取和修改它的程序代碼,以及要收集和摘要的網頁。 這是冗長系列中的第一個教學課程,將探索在 ASP.NET 2.0 中實作這些常見模式的技術。 我們將從使用具型別數據集、強制執行自定義商務規則的商業規則層,以及由共用通用版面配置之 ASP.NET 頁面組成的呈現層,開始建立 由數據存取層 (DAL) 組成的軟體架構 。 一旦配置此後端基礎之後,我們將移至報告,示範如何顯示、摘要、收集和驗證來自 Web 應用程式的數據。 這些教學課程旨在簡潔,並提供具有大量螢幕快照的逐步指示,以可視化方式引導您完成程式。 C# 和 Visual Basic 版本提供每個教學課程,並包含所使用完整程式碼的下載。 (第一個教學課程相當冗長,但其餘部分則以更易消化的區塊呈現。

針對這些教學課程,我們將使用放置在 App_Data 目錄中的 Microsoft SQL Server 2005 Express Edition 版本的 Northwind 資料庫。 除了資料庫檔案之外, App_Data 資料夾也包含用來建立資料庫的SQL腳本,以防您想要使用不同的資料庫版本。 如果您使用不同 SQL Server 版本的 Northwind 資料庫,則必須在應用程式的 Web.config 檔案中更新 NORTHWNDConnectionString 設定。 Web 應用程式是使用 Visual Studio 2005 Professional Edition 作為文件系統型網站專案所建置。 不過,所有教學課程都與 Visual Studio 2005 的免費 版本 Visual Web Developer 一起運作良好。

在本教學課程中,我們將從頭開始建立數據存取層 (DAL),然後在第二個教學課程中建立商業規則層 (BLL),並在第三個教學課程中處理頁面配置和流覽。 第三個之後的教學課程將建立在前三個基礎的基礎上。 我們在本第一個教學課程中有很多要討論的內容,所以請啟動Visual Studio,讓我們開始吧!

步驟 1:建立 Web 專案並連線到資料庫

在我們可以建立數據存取層 (DAL) 之前,我們必須先建立網站並設定資料庫。 從建立新的文件系統型 ASP.NET 網站開始。 若要達成此目的,請移至 [檔案] 功能表,然後選擇 [新增網站],顯示 [新增網站] 對話方塊。 選擇 [ASP.NET 網站範本]、將 [位置] 下拉式清單設定為 [檔案系統]、選擇要放置網站的資料夾,以及將語言設定為 C# 。

建立以文件系統為基礎的新網站

圖 1:建立新的檔案系統型網站(按一下以檢視完整大小的映射

這會建立具有Default.aspx ASP.NET 頁面和App_Data資料夾的新網站

建立網站后,下一個步驟是在Visual Studio的伺服器總管中新增資料庫的參考。 將資料庫新增至伺服器總管,即可從 Visual Studio 內新增數據表、預存程式、檢視等等。 您也可以透過查詢產生器手動或以圖形方式檢視數據表數據,或建立自己的查詢。 此外,當我們建置 DAL 的具型別數據集時,我們需要將 Visual Studio 指向應該建構具型別數據集的資料庫。 雖然我們可以在該時間點提供此連線資訊,但Visual Studio會自動填入伺服器總管中已註冊之資料庫的下拉式清單。

將 Northwind 資料庫新增至 [伺服器總管] 的步驟取決於您想要改用 App_Data 資料夾中的 SQL Server 2005 Express Edition 資料庫,或您是否有想要改用 Microsoft的 SQL Server 2000 或 2005 資料庫伺服器設定。

在 App_Data資料夾中使用資料庫

如果您沒有要連接的 SQL Server 2000 或 2005 資料庫伺服器,或只是想要避免將資料庫新增至資料庫伺服器,您可以使用位於下載網站的 App_Data 資料夾 (NORTHWND) 的 Northwind 資料庫的 SQL Server 2005 Express Edition 版本。MDF)。

放置在 App_Data 資料夾中的資料庫會自動新增至 [伺服器總管]。 假設您的計算機已安裝 SQL Server 2005 Express Edition,您應該會看到名為 NORTHWND 的節點。伺服器總管中的 MDF,您可以展開並探索其數據表、檢視表、預存程式等等(請參閱圖 2)。

App_Data資料夾也可以保存 Microsoft Access .mdb 檔案,就像其 SQL Server 對應項目一樣,會自動新增至 [伺服器總管]。 如果您不想使用任何 SQL Server 選項,您一律 可以安裝 Northwind Traders 資料庫和應用程式 ,並放入 App_Data 目錄中。 不過,請記住,Access 資料庫不如 SQL Server 功能豐富,而且不會設計成用於網站案例。 此外,有數個 35 個以上的教學課程會利用 Access 不支援的特定資料庫層級功能。

連線到 Microsoft SQL Server 2000 或 2005 資料庫伺服器中的資料庫

或者,您可以連線到安裝在資料庫伺服器上的 Northwind 資料庫。 如果資料庫伺服器尚未安裝 Northwind 資料庫,您必須先執行本教學課程下載中包含的安裝腳本,將其新增至資料庫伺服器。

安裝資料庫之後,請移至 Visual Studio 中的 [伺服器總管],以滑鼠右鍵按兩下 [數據連線] 節點,然後選擇 [新增連線]。 如果您沒有看到 [伺服器總管] 移至 [檢視/ 伺服器總管],或按 Ctrl+Alt+S。 這會顯示 [新增連線] 對話框,您可以在其中指定要連接的伺服器、驗證資訊,以及資料庫名稱。 成功設定資料庫連接資訊並按兩下 [確定] 按鈕后,資料庫將會新增為 [資料連線] 節點底下的節點。 您可以展開資料庫節點,以探索其數據表、檢視表、預存程式等等。

將連線新增至資料庫伺服器的 Northwind 資料庫

圖 2:將連線新增至資料庫伺服器的 Northwind 資料庫

步驟 2:建立數據存取層

使用數據時,其中一個選項是將數據特定邏輯直接內嵌至呈現層(在 Web 應用程式中,ASP.NET 頁面構成表示層)。 這可能採用在 ASP.NET 頁面的程式代碼部分或使用標記部分的 SqlDataSource 控制件撰寫 ADO.NET 程式代碼的形式。 不論是哪一種情況,此方法都緊密結合數據存取邏輯與表示層。 不過,建議的方法是將數據存取邏輯與表示層分開。 此個別層稱為「數據存取層」、「DAL」,通常實作為個別的「類別庫」專案。 此分層架構的優點已妥善記載(請參閱本教學課程結尾的一節,以取得這些優點的相關信息),也是我們將在此系列中採用的方法。

基礎數據源專屬的所有程序代碼,例如建立資料庫的連線、發出 SELECTINSERTUPDATEDELETE 命令等等,都應該位於 DAL 中。 表示層不應包含這類數據存取程式代碼的任何參考,而是應該針對任何和所有數據要求呼叫 DAL。 數據存取層通常包含存取基礎資料庫數據的方法。 例如,Northwind 資料庫具有 ProductsCategories 數據表,可記錄要銷售的產品及其所屬類別。 在我們的 DAL 中,我們將有如下的方法:

  • GetCategories(), 這會傳回所有類別的相關信息
  • GetProducts(),它會傳回所有產品的相關信息
  • GetProductsByCategoryID(categoryID,它會傳回屬於指定類別的所有產品
  • GetProductByProductID(productID,它會傳回特定產品的相關信息

叫用時,這些方法會連線到資料庫、發出適當的查詢,並傳回結果。 我們如何傳回這些結果很重要。 這些方法可能只會傳回資料庫查詢所填入的 DataSet 或 DataReader,但最好使用這些 強型別物件傳回這些結果。 強型別對像是一個在編譯時期嚴格定義的架構,而相反的是鬆散型別的物件,就是在運行時間之前不知道其架構的物件。

例如,DataReader 和 DataSet (預設)是鬆散類型的物件,因為它們的架構是由用來填入它們的資料庫查詢所傳回的數據行所定義。 若要從鬆散類型的 DataTable 存取特定數據行,我們需要使用如下的語法: DataTable。Rows[index][“columnName”]。 在此範例中,DataTable 的鬆散輸入是透過我們需要使用字串或序數索引來存取數據行名稱的事實所展示。 另一方面,強型別的 DataTable 會將其每個數據行實作為屬性,導致程式代碼看起來像: DataTable。Rows[index]。columnName

若要傳回強型別對象,開發人員可以建立自己的自定義商務物件或使用具類型的數據集。 商務物件是由開發人員實作為類別,其屬性通常會反映商務物件所代表之基礎資料庫數據表的數據行。 具類型的 DataSet 是 Visual Studio 根據資料庫架構為您產生的類別,其成員會根據這個架構進行強型別。 具型別的 DataSet 本身是由擴充 ADO.NET DataSet、DataTable 和 DataRow 類別的類別所組成。 除了強型別的 DataTable 之外,具型別的 DataSets 現在也包含 TableAdapters,這些數據表是類別,其中包含填入 DataSet 的 DataTable 的方法,並將 DataTables 內的修改傳播回資料庫。

注意

如需使用具型別數據集與自定義商務物件之優缺點的詳細資訊,請參閱 設計數據層元件和透過層傳遞數據。

我們將針對這些教學課程的架構使用強型別數據集。 圖 3 說明使用具型別數據集之應用程式不同層級之間的工作流程。

所有數據存取代碼都會降級為 DAL

圖 3:所有資料存取代碼都會降級為 DAL (按兩下以檢視完整大小的影像

建立具類型的數據集和數據表配接器

若要開始建立 DAL,首先將具類型的 DataSet 新增至專案。 若要達成此目的,請以滑鼠右鍵按兩下 方案總管 中的項目節點,然後選擇 [新增專案]。 從範本清單中選取 [資料集] 選項,並將其命名為 Northwind.xsd

選擇將新的數據集新增至您的專案

圖 4:選擇將新的資料集新增至您的專案(按兩下以檢視完整大小的影像

按兩下 [新增] 之後,當系統提示您將DataSet新增至 App_Code 資料夾時,請選擇[是]。 然後會顯示具型別數據集的設計工具,而 TableAdapter 組態精靈將會啟動,讓您將第一個 TableAdapter 新增至具類型的數據集。

具型別的數據集可作為強型別的數據收集;它是由強型別的DataTable實例所組成,每個實例都會由強型別的DataRow實例組成。 我們將針對每個基礎資料庫數據表建立強型別的 DataTable,我們需要在本教學課程系列中加以使用。 讓我們從建立 Products 數據表的 DataTable 開始。

請記住,強型別的 DataTable 不包含如何從基礎資料庫數據表存取數據的任何資訊。 為了擷取數據以填入DataTable,我們使用TableAdapter類別,其可作為資料存取層。 針對我們的 Products DataTable,TableAdapter 將包含 GetProducts()GetProductByCategoryID(categoryID等方法,以便從表示層叫用。 DataTable 的角色是做為用來在圖層之間傳遞數據的強型別物件。

TableAdapter 組態精靈會先提示您選取要使用的資料庫。 下拉式清單會在 [伺服器總管] 中顯示這些資料庫。 如果您未將 Northwind 資料庫新增至 [伺服器總管],您可以按兩下此時的 [新增連線] 按鈕來執行此動作。

從下拉式清單中選擇 Northwind 資料庫

圖 5:從下拉式清單中選擇 Northwind 資料庫 (按兩下以檢視完整大小的影像

選取資料庫並按兩下一步, 之後,系統會詢問您是否要將 連接字串 儲存在Web.config 檔案中。 藉由儲存 連接字串,您將避免在 TableAdapter 類別中硬式編碼,這可簡化未來 連接字串 資訊變更的情況。 如果您選擇將 連接字串 儲存在<組態檔中,則會將其放在 connectionStrings> 區段中,透過 IIS GUI 管理工具中新的 ASP.NET 2.0 屬性頁進行選擇性加密,以便稍後透過新的 ASP.NET 2.0 屬性頁來修改,這更適合系統管理員。

將連接字串儲存至 Web.config

圖 6:將連接字串儲存至 Web.config按兩下以檢視完整大小的影像

接下來,我們需要定義第一個強型別 DataTable 的架構,並提供填入強型別 DataSet 時要使用的 TableAdapter 第一個方法。 這兩個步驟可藉由建立查詢,從我們想要反映在 DataTable 中的數據表傳回數據行,以同時完成。 在精靈結尾,我們將為此查詢提供方法名稱。 完成之後,就可以從我們的表示層叫用這個方法。 方法會執行定義的查詢,並填入強型別的 DataTable。

若要開始定義 SQL 查詢,我們必須先指出 TableAdapter 發出查詢的方式。 我們可以使用臨機操作 SQL 語句、建立新的預存程式,或使用現有的預存程式。 針對這些教學課程,我們將使用臨機操作 SQL 語句。

使用臨機操作 SQL 語句查詢數據

圖 7:使用臨機操作 SQL 語句查詢資料 (按兩下以檢視完整大小的影像

此時,我們可以手動輸入 SQL 查詢。 在 TableAdapter 中建立第一個方法時,您通常想要讓查詢傳回需要在對應 DataTable 中表示的數據行。 我們可以藉由建立查詢,從 Products 數據表傳回所有數據行和所有數據列來達成此目的:

在文字框中輸入 SQL 查詢

圖 8:在文字框中輸入 SQL 查詢 (按兩下以檢視完整大小的影像

或者,使用查詢產生器並以圖形方式建構查詢,如圖 9 所示。

透過 查詢編輯器 以圖形方式建立查詢

圖 9:透過 查詢編輯器 以圖形方式建立查詢(按兩下以檢視完整大小的影像

建立查詢之後,但在移至下一個畫面之前,按兩下 [進階選項] 按鈕。 在網站專案中,[產生插入、更新及刪除語句] 是預設唯一選取的進階選項:如果您從類別庫或 Windows 專案執行此精靈,也會選取 [使用開放式並行存取] 選項。 目前未核取 [使用開放式並行存取] 選項。 我們將在未來的教學課程中檢查開放式並行存取。

僅選取 [產生插入]、[更新] 和 [刪除語句選項]

圖 10:僅選取 [產生插入]、[更新] 和 [刪除語句] 選項 (按兩下以檢視完整大小的影像

確認進階選項之後,按 [下一步] 繼續進行最終畫面。 我們在這裡被要求選取要新增至 TableAdapter 的方法。 填入資料有兩種模式:

  • 使用此方法填入 DataTable ,會建立方法,以 DataTable 做為參數,並根據查詢的結果填入它。 例如,ADO.NET DataAdapter 類別會使用其 Fill() 方法實作此模式。
  • 使用此方法傳回 DataTable ,此方法會為您建立並填入 DataTable,並在方法傳回值時傳回它。

您可以讓 TableAdapter 實作其中一種或兩種模式。 您也可以重新命名這裡提供的方法。 讓我們保留這兩個複選框,即使我們只會在這些教學課程中使用後者模式。 此外,讓我們將相當一般的 GetData 方法重新命名為 GetProducts

如果核取,則最後一個複選框 「GenerateDBDirectMethods」 會建立 TableAdapter 的 Insert()Update()Delete() 方法。 如果您取消核取此選項,所有更新都必須透過 TableAdapter 的唯 一 Update() 方法來完成,此方法會採用具類型的 DataSet、DataTable、單一 DataRow 或 DataRows 陣列。 (如果您已從圖 9 的進階屬性取消核取 [產生插入]、[更新] 和 [刪除語句] 選項,此複選框的設定將不會有任何作用。讓我們選取此複選框。

將方法名稱從 GetData 變更為 GetProducts

圖 11:將方法名稱從 GetData 變更為 GetProducts按兩下以檢視完整大小的影像

按兩下 [完成] 來完成精靈。 精靈關閉之後,我們會返回 DataSet 設計工具,其中顯示我們剛才建立的 DataTable。 您可以在 Products DataTable(ProductIDProductName 等)以及 ProductsTableAdapterFill()GetProducts()的方法中看到數據行清單

Products DataTable 和 ProductsTableAdapter 已新增至具類型的數據集

圖 12:產品 DataTable 和 ProductsTableAdapter 已新增至具類型的數據集(按兩下以檢視完整大小的影像)

此時,我們有具有單一 DataTable (Northwind.Products) 的具型別數據集,以及具有 GetProducts() 方法的強型別 DataAdapter 類別(NorthwindTableAdapters.ProductsTableAdapter)。 這些物件可用來從程式代碼存取所有產品的清單,例如:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;
products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow productRow in products)
    Response.Write("Product: " + productRow.ProductName + "<br />");

此程式代碼不需要我們撰寫一位數據存取特定程序代碼。 我們不需要具現化任何 ADO.NET 類別,我們不需要參考任何 連接字串、SQL 查詢或預存程式。 相反地,TableAdapter 會為我們提供低階數據存取程序代碼。

此範例中使用的每個物件也是強型別,可讓Visual Studio提供IntelliSense和編譯時間類型檢查。 TableAdapter 傳回的所有 DataTable 都可以系結至 ASP.NET 數據 Web 控件,例如 GridView、DetailsView、DropDownList、CheckBoxList 和其他數個控件。 下列範例說明將 GetProducts() 方法傳回的 DataTable 系結至 GridView,而只有Page_Load事件處理程式內的三行程式代碼。

AllProducts.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AllProducts.aspx.cs"
    Inherits="AllProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>View All Products in a GridView</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>
            All Products</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

AllProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class AllProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource = productsAdapter.GetProducts();
        GridView1.DataBind();
    }
}

產品清單會顯示在 GridView 中

圖 13:產品清單會顯示在 GridView 中(按兩下以檢視完整大小的影像

雖然此範例需要我們在 ASP.NET 頁面 的 Page_Load 事件處理程式中撰寫三行程序代碼,但在未來的教學課程中,我們將檢查如何使用 ObjectDataSource 以宣告方式從 DAL 擷取數據。 使用 ObjectDataSource 時,我們不需要撰寫任何程式代碼,也會收到分頁和排序支援!

步驟 3:將參數化方法新增至數據存取層

此時,ProductsTableAdapter 類別有一個方法 GetProducts(),它會傳回資料庫中的所有產品。 雖然能夠使用所有產品絕對有用,但有時我們想要擷取特定產品的相關信息,或屬於特定類別的所有產品。 若要將這類功能新增至數據存取層,我們可以將參數化方法新增至 TableAdapter。

讓我們新增 GetProductsByCategoryID(categoryID 方法。 若要將新方法新增至 DAL,請返回 DataSet 設計工具,在 ProductsTableAdapter 區段中按兩下滑鼠右鍵,然後選擇 [新增查詢]。

以滑鼠右鍵按下 TableAdapter 並選擇 [新增查詢]

圖 14:以滑鼠右鍵按下 TableAdapter 並選擇 [新增查詢]

我們會先提示我們是否要使用臨機操作 SQL 語句或新的或現有的預存程式來存取資料庫。 讓我們選擇再次使用臨機操作 SQL 語句。 接下來,系統會詢問我們想要使用的 SQL 查詢類型。 由於我們想要傳回屬於指定類別的所有產品,因此我們想要撰寫 會傳回數據列的SELECT 語句。

選擇建立會傳回數據列的 SELECT 語句

圖 15:選擇建立 會傳回數據列的 SELECT 語句(單擊以檢視完整大小的影像

下一個步驟是定義用來存取數據的 SQL 查詢。 由於我們只想要傳回屬於特定類別的產品,我從 GetProducts()使用相同的 SELECT 語句,但新增下列 WHERE 子句:WHERE CategoryID = @CategoryID @CategoryID參數會向 TableAdapter 精靈指出,我們正在建立的方法需要對應類型的輸入參數(也就是可為 Null 的整數)。

輸入查詢以只傳回指定類別中的產品

圖 16:輸入查詢以僅傳回指定類別中的產品 (按兩下以檢視完整大小的影像

在最後一個步驟中,我們可以選擇要使用的數據存取模式,以及自定義產生的方法名稱。 針對 Fill 模式,讓我們將名稱變更為 FillByCategoryID,並針對傳回 DataTable 傳回模式 (GetX 方法),讓我們使用 GetProductsByCategoryID

選擇 TableAdapter 方法的名稱

圖 17:選擇 TableAdapter 方法的名稱(按兩下以檢視完整大小的影像

完成精靈之後,DataSet 設計工具會包含新的 TableAdapter 方法。

產品現在可以依類別查詢

圖 18:產品現在可以依類別查詢

請花點時間使用相同的技術新增 GetProductByProductID(productID 方法。

您可以從 DataSet 設計工具直接測試這些參數化查詢。 以滑鼠右鍵按下 TableAdapter 中的 方法,然後選擇 [預覽數據]。 接下來,輸入要用於參數的值,然後按兩下 [預覽]。

屬於飲料類別的這些產品會顯示

圖 19:顯示屬於飲料類別的產品(按兩下以檢視全尺寸影像

透過 DAL 中的 GetProductsByCategoryID(categoryID 方法,我們現在可以建立 ASP.NET 頁面,只顯示指定類別中的這些產品。 下列範例顯示 [飲料] 類別中的所有產品,其 CategoryID 為 1。

Beverages.asp

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Beverages.aspx.cs"
    Inherits="Beverages" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>Beverages</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

Beverages.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class Beverages : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new
         ProductsTableAdapter();
        GridView1.DataSource =
          productsAdapter.GetProductsByCategoryID(1);
        GridView1.DataBind();
    }
}

[飲料類別] 中的這些產品會顯示

圖 20:顯示 [飲料類別] 中的這些產品(按兩下以檢視全尺寸影像

步驟 4:插入、更新和刪除數據

有兩種模式通常用於插入、更新和刪除數據。 第一個模式,我會呼叫資料庫直接模式,牽涉到建立方法,在叫用時,向在單一 資料庫記錄上操作的資料庫發出 INSERTUPDATEDELETE 命令。 這類方法通常會傳入一系列純量值(整數、字串、布爾值、DateTimes 等等),以對應至要插入、更新或刪除的值。 例如,使用 Products 數據表的這個模式時,delete 方法會採用整數參數,指出要刪除之記錄的 ProductID,而 insert 方法會採用 ProductName 的字元串、UnitPrice 的十進位數、UnitsOnStock整數等等。

每個插入、更新和刪除要求都會立即傳送至資料庫

圖 21:每個插入、更新和刪除要求都會立即傳送至資料庫(按兩下以檢視完整大小的影像

另一種模式,我將稱為批次更新模式,是在一個方法呼叫中更新整個 DataSet、DataTable 或 DataRows 集合。 使用此模式時,開發人員會刪除、插入及修改 DataTable 中的 DataRows,然後將這些 DataRows 或 DataTable 傳遞至更新方法。 然後,這個方法會列舉傳入的 DataRows、判斷它們是否已修改、新增或刪除(透過 DataRow 的 RowState 屬性值 ),並針對每個記錄發出適當的資料庫要求。

叫用 Update 方法時,所有變更都會與資料庫同步處理

圖 22:叫用更新方法時,所有變更都會與資料庫同步處理(按兩下以檢視完整大小的影像

TableAdapter 預設會使用批次更新模式,但也支援 DB 直接模式。 由於我們在建立 TableAdapter 時,從進階屬性中選取 [產生插入、更新及刪除語句] 選項, ProductsTableAdapter 包含 Update() 方法,可實作批次更新模式。 具體而言,TableAdapter 包含一個 Update() 方法,可以傳遞具類型的 DataSet、強型別的 DataTable 或一或多個 DataRows。 如果您在第一次建立 TableAdapter 時,已核取 [GenerateDBDirectMethods] 複選框,DB 直接模式也會透過 Insert()Update()Delete() 方法來實作。

這兩種數據修改模式都會使用 TableAdapter 的 InsertCommand、UpdateCommandDeleteCommand 屬性,向資料庫發出 INSERTUPDATEDELETE 命令。 您可以按下 DataSet 設計工具中的 TableAdapter,然後移至 屬性視窗,檢查及修改 InsertCommand、UpdateCommandDeleteCommand 屬性。 (請確定您已選取 TableAdapter,且ProductsTableAdapter 物件是在 屬性視窗 的下拉式清單中選取的物件。

TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性

圖 23:TableAdapter 具有 InsertCommand、UpdateCommandDeleteCommand 属性(按兩下以檢視完整大小的影像

若要檢查或修改其中任何一個資料庫命令屬性,請按兩下 [CommandText ] 子屬性,這會啟動查詢產生器。

在查詢產生器中設定 INSERT、UPDATE 和 DELETE 語句

圖 24:在查詢產生器中設定 INSERTUPDATEDELETE 語句(單擊以檢視完整大小的影像

下列程式代碼範例示範如何使用批次更新模式,將未停產且庫存不足 25 個單位的所有產品價格加倍:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
  new NorthwindTableAdapters.ProductsTableAdapter();
// For each product, double its price if it is not discontinued and
// there are 25 items in stock or less
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
   if (!product.Discontinued && product.UnitsInStock <= 25)
      product.UnitPrice *= 2;
// Update the products
productsAdapter.Update(products);

下列程式代碼說明如何使用 DB 直接模式,以程式設計方式刪除特定產品,然後更新一個產品,然後新增一個:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Delete the product with ProductID 3
productsAdapter.Delete(3);
// Update Chai (ProductID of 1), setting the UnitsOnOrder to 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags",
  18.0m, 39, 15, 10, false, 1);
// Add a new product
productsAdapter.Insert("New Product", 1, 1,
  "12 tins per carton", 14.95m, 15, 0, 10, false);

建立自定義插入、更新和刪除方法

DB 直接方法所建立的 Insert()Update()Delete() 方法可能有點麻煩,特別是對於具有許多數據行的數據表。 查看先前的程式代碼範例,如果沒有 IntelliSense 的協助,則不會特別清楚哪些 Products 數據表數據行對應至 Update()Insert() 方法的每個輸入參數。 有時候,我們只想更新單一數據行或兩個數據行,或想要自定義的 Insert() 方法,或許會傳回新插入記錄的 IDENTITY (自動遞增) 欄位的值。

若要建立這類自定義方法,請返回DataSet設計工具。 以滑鼠右鍵按下 TableAdapter,然後選擇 [新增查詢],返回 TableAdapter 精靈。 在第二個畫面上,我們可以指出要建立的查詢類型。 讓我們建立加入新產品的方法,然後傳回新加入記錄的 ProductID 值。 因此,選擇建立 INSERT 查詢。

建立方法以將新數據列新增至 Products 數據表

圖 25:建立將新數據列新增至 產品 數據表的方法(按兩下以檢視完整大小的影像

在下一個畫面上, 會出現 InsertCommandCommandText 。 藉由在查詢結尾新增 SELECT SCOPE_IDENTITY() 來增強此查詢,這會傳回在相同範圍中插入至 IDENTITY 數據行的最後一個識別值。 (如需SCOPE_IDENTITY() 的詳細資訊,請參閱技術檔,以及您可能想要使用 SCOPE_IDENTITY() 而不是@@IDENTITY。 在新增 SELECT 語句之前,請務必使用分號結束 INSERT 語句。

增強查詢以傳回 SCOPE_IDENTITY() 值

圖 26:增強查詢以傳回 SCOPE_IDENTITY() 值(按兩下以檢視完整大小的影像

最後,將新方法 命名為 InsertProduct

將 [新增方法名稱] 設定為 InsertProduct

圖 27:將 [新增方法名稱] 設定為 InsertProduct按兩下以檢視完整大小的影像

當您返回 DataSet 設計工具時,您會看到 ProductsTableAdapter 包含新的方法 InsertProduct。 如果這個新方法沒有 Products 數據表中每個數據行的參數,您很可能忘記以分號終止 INSERT 語句。 設定 InsertProduct 方法,並確定您有以分號分隔 INSERTSELECT 語句。

根據預設,插入方法會發出非查詢方法,這表示它們會傳回受影響的數據列數目。 不過,我們希望 InsertProduct 方法傳回查詢所傳回的值,而不是受影響的數據列數目。 若要達成此目的,請將 InsertProduct 方法的 ExecuteMode 屬性調整為 Scalar

將 ExecuteMode 屬性變更為 Scalar

圖 28:將 ExecuteMode 屬性變更為純量按兩下以檢視完整大小的影像

下列程式代碼顯示這個新的 InsertProduct 方法運作方式:

NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
    new NorthwindTableAdapters.ProductsTableAdapter();
// Add a new product
int new_productID = Convert.ToInt32(productsAdapter.InsertProduct
    ("New Product", 1, 1, "12 tins per carton", 14.95m, 10, 0, 10, false));
// On second thought, delete the product
productsAdapter.Delete(new_productID);

步驟 5:完成數據存取層

請注意,ProductsTableAdapters 類別會從 Products 數據表傳回 CategoryIDSupplierID 值,但不包含來自 Categories 數據表的 CategoryName 數據行或 Supplier 數據表中的 CompanyName 數據行,雖然這可能是我們想要顯示產品資訊時要顯示的數據行。 我們可以增強 TableAdapter 的初始方法 GetProducts(),以同時包含 CategoryNameCompanyName 數據行值,這會更新強型別的 DataTable 以包含這些新數據行。

不過,這可能會產生問題,因為 TableAdapter 插入、更新和刪除數據的方法是以這個初始方法為基礎。 幸運的是,自動產生的插入、更新和刪除方法不會受到 SELECT 子句中的子查詢影響。 藉由小心將查詢新增至 CategoriesSuppliers 作為子查詢,而不是 JOIN ,我們將避免必須重新處理這些方法來修改數據。 以滑鼠右鍵按兩下 ProductsTableAdapter 中的 GetProducts() 方法,然後選擇 [設定]。 然後,調整 SELECT 子句,使其看起來像:

SELECT     ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM         Products

更新 GetProducts 方法的 SELECT 語句

圖 29:更新 GetProducts 方法的 SELECT 語句(按兩下以檢視完整大小的影像)

更新 GetProducts() 方法以使用此新查詢之後,DataTable 會包含兩個新的數據行: CategoryNameSupplierName

Products DataTable 有兩個新的數據行

圖 30Products DataTable 有兩個新的數據行

請花點時間更新 GetProductsByCategoryID(categoryID 方法中的 SELECT 子句。

如果您使用 JOIN 語法更新 GetProducts() SELECT,DataSet Designer 將無法使用 DB 直接模式自動產生插入、更新和刪除資料庫數據 的方法。 相反地,您必須手動建立它們,就像我們稍早在本教學課程中使用 InsertProduct 方法一樣。 此外,如果您想要使用批次更新模式,您必須手動提供 InsertCommand、UpdateCommandDeleteCommand 屬性值。

新增剩餘的 TableAdapters

到目前為止,我們只查看使用單一資料庫數據表的單一 TableAdapter。 不過,Northwind 資料庫包含數個相關數據表,我們需要在 Web 應用程式中使用。 具類型的數據集可以包含多個相關的 DataTable。 因此,若要完成 DAL,我們需要新增其他數據表的 DataTable,我們將在這些教學課程中使用。 若要將新的 TableAdapter 新增至具類型的數據集,請開啟 DataSet 設計工具,在設計師中按兩下滑鼠右鍵,然後選擇 [新增/ TableAdapter]。 這會建立新的 DataTable 和 TableAdapter,並逐步引導您完成本教學課程稍早所檢查的精靈。

請花幾分鐘的時間,使用下列查詢建立下列 TableAdapters 和方法。 請注意,ProductsTableAdapter 中的查詢包含子查詢,以擷取每個產品的類別和供應商名稱。 此外,如果您一直在追蹤,您已經新增 ProductsTableAdapter 類別的 GetProducts()GetProductsByCategoryID(categoryID 方法。

  • ProductsTableAdapter

    • GetProducts

      SELECT     ProductID, ProductName, SupplierID, 
      CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, 
      UnitsOnOrder, ReorderLevel, Discontinued, 
      (SELECT CategoryName FROM Categories WHERE
      Categories.CategoryID = Products.CategoryID) as 
      CategoryName, (SELECT CompanyName FROM Suppliers
      WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      
    • GetProductsByCategoryID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName,
      (SELECT CompanyName FROM Suppliers WHERE
      Suppliers.SupplierID = Products.SupplierID)
      as SupplierName
      FROM         Products
      WHERE      CategoryID = @CategoryID
      
    • GetProductsBySupplierID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE 
      Suppliers.SupplierID = Products.SupplierID) as SupplierName
      FROM         Products
      WHERE SupplierID = @SupplierID
      
    • GetProductByProductID

      SELECT     ProductID, ProductName, SupplierID, CategoryID,
      QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
      ReorderLevel, Discontinued, (SELECT CategoryName 
      FROM Categories WHERE Categories.CategoryID = 
      Products.CategoryID) as CategoryName, 
      (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID) 
      as SupplierName
      FROM         Products
      WHERE ProductID = @ProductID
      
  • CategoriesTableAdapter

    • GetCategories

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      
    • GetCategoryByCategoryID

      SELECT     CategoryID, CategoryName, Description
      FROM         Categories
      WHERE CategoryID = @CategoryID
      
  • SuppliersTableAdapter

    • GetSuppliers

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      
    • GetSuppliersByCountry

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE Country = @Country
      
    • GetSupplierBySupplierID

      SELECT     SupplierID, CompanyName, Address,
      City, Country, Phone
      FROM         Suppliers
      WHERE SupplierID = @SupplierID
      
  • EmployeesTableAdapter

    • GetEmployees

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      
    • GetEmployeesByManager

      SELECT     EmployeeID, LastName, FirstName, Title, 
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE ReportsTo = @ManagerID
      
    • GetEmployeeByEmployeeID

      SELECT     EmployeeID, LastName, FirstName, Title,
      HireDate, ReportsTo, Country
      FROM         Employees
      WHERE EmployeeID = @EmployeeID
      

新增四個 TableAdapters 之後的數據集設計工具

圖 31:新增四個 TableAdapters 之後的數據集設計工具(按兩下以檢視完整大小的影像

將自定義程式代碼新增至 DAL

新增至具型別數據集的 TableAdapters 和 DataTable 會以 XML 架構定義檔案 (Northwind.xsd) 表示。 您可以用滑鼠右鍵按兩下 方案總管中的 Northwind.xsd 檔案,然後選擇 [檢視程式代碼] 來檢視此架構資訊。

Northwinds 具型別數據集的 XML 架構定義 (XSD) 檔案

圖 32:Northwinds 具型別數據集的 XML 架構定義 (XSD) 檔案 (按兩下以檢視完整大小的影像

此架構資訊會在編譯或運行時間(如有需要)在設計時間轉譯成 C# 或 Visual Basic 程式代碼,此時您可以使用調試程式逐步執行。 若要檢視此自動產生的程式代碼,請移至 [類別檢視],然後向下切入至 TableAdapter 或 Typed DataSet 類別。 如果您沒有在畫面上看到 [類別檢視],請移至 [檢視] 功能表,然後從該處選取它,或按 Ctrl+Shift+C。 從 [類別檢視] 中,您可以看到具型別 DataSet 和 TableAdapter 類別的屬性、方法和事件。 若要檢視特定方法的程式代碼,請按兩下 [類別檢視] 中的方法名稱,或以滑鼠右鍵按兩下它,然後選擇 [移至定義]。

從類別檢視選取 [移至定義] 來檢查自動產生的程序代碼

圖 33:從類別檢視選取 [移至定義] 來檢查自動產生的程式代碼

雖然自動產生的程式代碼可能是絕佳的節省時間,但程式代碼通常非常泛型,而且需要自定義以符合應用程式的獨特需求。 不過,擴充自動產生的程式代碼的風險是,產生程式碼的工具可能會決定要「重新產生」並覆寫您的自定義專案。 使用 .NET 2.0 的新部分類別概念,可以輕鬆地將類別分割成多個檔案。 這可讓我們將自己的方法、屬性和事件新增至自動產生的類別,而不必擔心Visual Studio會覆寫我們的自定義。

為了示範如何自定義 DAL,讓我們將 GetProducts() 方法新增SuppliersRow 類別。 SuppliersRow 類別代表供應商數據表中的單一記錄;每個供應商可以提供者零到許多產品,因此 GetProducts() 會傳回指定供應商的這些產品。 若要完成此作業,請在名為 SuppliersRow.cs 的 App_Code 資料夾中建立新的類別檔案,並新增下列程序代碼:

using System;
using System.Data;
using NorthwindTableAdapters;
public partial class Northwind
{
    public partial class SuppliersRow
    {
        public Northwind.ProductsDataTable GetProducts()
        {
            ProductsTableAdapter productsAdapter =
             new ProductsTableAdapter();
            return
              productsAdapter.GetProductsBySupplierID(this.SupplierID);
        }
    }
}

這個部分類別會指示編譯程式建置 Northwind.SuppliersRow 類別 時,我們剛才定義的 GetProducts() 方法。 如果您建置專案,然後返回類別檢視,您會看到 GetProducts() 現在列為 Northwind.SuppliersRow 的方法

GetProducts() 方法現在是 Northwind.SuppliersRow 類別的一部分

圖 34GetProducts() 方法現在是 Northwind.SuppliersRow 類別的一部分

GetProducts() 方法現在可用來列舉特定供應商的產品集,如下列程式代碼所示:

NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter =
    new NorthwindTableAdapters.SuppliersTableAdapter();
// Get all of the suppliers
Northwind.SuppliersDataTable suppliers =
  suppliersAdapter.GetSuppliers();
// Enumerate the suppliers
foreach (Northwind.SuppliersRow supplier in suppliers)
{
    Response.Write("Supplier: " + supplier.CompanyName);
    Response.Write("<ul>");
    // List the products for this supplier
    Northwind.ProductsDataTable products = supplier.GetProducts();
    foreach (Northwind.ProductsRow product in products)
        Response.Write("<li>" + product.ProductName + "</li>");
    Response.Write("</ul><p> </p>");
}

此數據也可以顯示在任何 ASP 中。NET 的數據 Web 控制件。 下列頁面使用具有兩個字段的 GridView 控件:

  • 顯示每個供應商名稱的 BoundField,以及
  • TemplateField,其中包含一個 BulletedList 控件,該控件系結至每個供應商的 GetProducts() 方法所傳回的結果。

我們將檢查如何在未來的教學課程中顯示這類主要詳細數據報告。 目前,此範例的設計目的是說明如何使用新增至 Northwind.SuppliersRow 類別的自定義方法。

SuppliersAndProducts.aspx

<%@ Page Language="C#" CodeFile="SuppliersAndProducts.aspx.cs"
    AutoEventWireup="true" Inherits="SuppliersAndProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h2>
            Suppliers and Their Products</h2>
        <p>
            <asp:GridView ID="GridView1" runat="server"
             AutoGenerateColumns="False"
             CssClass="DataWebControlStyle">
                <HeaderStyle CssClass="HeaderStyle" />
                <AlternatingRowStyle CssClass="AlternatingRowStyle" />
                <Columns>
                    <asp:BoundField DataField="CompanyName"
                      HeaderText="Supplier" />
                    <asp:TemplateField HeaderText="Products">
                        <ItemTemplate>
                            <asp:BulletedList ID="BulletedList1"
                             runat="server" DataSource="<%# ((Northwind.SuppliersRow) ((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>"
                                 DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
             </p>
    </div>
    </form>
</body>
</html>

SuppliersAndProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class SuppliersAndProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SuppliersTableAdapter suppliersAdapter = new
          SuppliersTableAdapter();
        GridView1.DataSource = suppliersAdapter.GetSuppliers();
        GridView1.DataBind();
    }
}

供應商的公司名稱列在左欄中,其產品在右側

圖 35:供應商的公司名稱列在左欄中,其產品在右方 (按兩下以檢視完整大小的影像

摘要

建立建立 DAL 的 Web 應用程式應該是您的第一個步驟之一,在開始建立表示層之前發生。 使用 Visual Studio,根據具類型數據集建立 DAL 是一項工作,可在 10-15 分鐘內完成,而不需要撰寫一行程式代碼。 前進的教學課程將以此 DAL 為基礎。 在下一個教學課程中,我們將定義一些商務規則,並瞭解如何在不同的商業規則層中實作規則。

快樂的程序設計!

深入閱讀

如需本教學課程中所討論主題的詳細資訊,請參閱下列資源:

本教學課程中包含的主題影片訓練

關於作者

斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是 山姆斯在24小時內 ASP.NET 2.0。 他可以到達 mitchell@4GuysFromRolla.com, 或通過他的博客,可以在 找到 http://ScottOnWriting.NET

特別感謝

本教學課程系列已由許多實用的檢閱者檢閱。 本教學課程的主要檢閱者是 Ron Green、Hilton Giesenow、Dennis Patterson、Liz Shulok、Abel Gomez 和 Carlos Santos。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請將一行 mitchell@4GuysFromRolla.com放在 。