建立資料存取層 (C#)

作者 :Scott Mitchell

下載 PDF

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

簡介

身為 Web 開發人員,我們的生活會圍繞使用數據。 我們會建立資料庫來儲存數據、用來擷取和修改的程式代碼,以及收集並摘要它的網頁。 這是冗長系列中的第一個教學課程,將探索在 ASP.NET 2.0 中實作這些常見模式的技術。 我們將從使用具類型數據集、強制執行自定義商務規則的商務 (規則層) BLL) ,以及由共用通用頁面版面配置 ASP.NET 頁面組成的呈現層,開始建立由 Data Access Layer (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# 。

System-Based 網站建立新檔案

圖 1:建立新的檔案 System-Based 網站 (按兩下即可檢視完整大小的影像)

這會建立具有 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資料夾中的 Northwind 資料庫 SQL Server 2005 Express Edition 版本, (NORTHWND。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 中的 [伺服器總管],以滑鼠右鍵按兩下 [資料 Connections] 節點,然後選擇 [新增連線]。 如果您沒有看到 [伺服器總管],請移至 [檢視/伺服器總管],或按 Ctrl+Alt+S。 這會顯示 [新增連線] 對話框,您可以在其中指定要連接的伺服器、驗證資訊和資料庫名稱。 成功設定資料庫連接資訊並按兩下 [確定] 按鈕之後,資料庫將會新增為 [資料 Connections] 節點底下的節點。 您可以展開資料庫節點來探索其數據表、檢視、預存程式等等。

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

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

步驟 2:建立數據存取層

使用數據時,其中一個選項是直接將數據特定邏輯內嵌至 Web 應用程式中的呈現層 (,ASP.NET 頁面會構成呈現層) 。 這可能採用在 ASP.NET 頁面的程式代碼部分或使用標記部分的 SqlDataSource 控制件撰寫 ADO.NET 程式代碼的形式。 不論是哪一種情況,此方法都緊密結合數據存取邏輯與表示層。 不過,建議的方法是分隔數據存取邏輯與呈現層。 此個別層稱為「數據存取層」、「DAL」,簡稱為 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 之外,具型別的 DataSet 現在也包含 TableAdapters,這些類別包含用來填入 DataSet 的 DataTable,並將 DataTable 中的修改傳播回資料庫。

注意

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

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

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

圖 3:所有數據存取碼都會重新分派至 DAL (按兩下即可檢視完整大小的影像)

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

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

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

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

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

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

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

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

從 Drop-Down 清單中選擇 Northwind 資料庫

圖 5:從 [Drop-Down 列表] 選擇 Northwind 資料庫, (按兩下即可檢視大小完整的影像)

選取資料庫並按兩下一步, 之後,系統會詢問您是否要將 連接字串 儲存在 Web.config 檔案中。 藉由儲存 連接字串,您將避免在 TableAdapter 類別中硬式編碼,這可簡化未來 連接字串 資訊變更時的內容。 如果您選擇將 連接字串 儲存在組態檔中<,它會放在 connectionStrings> 區段中,您可以選擇性地加密以改善安全性,或稍後透過 IIS GUI 管理員 Tool 中的新 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 Designer,其中顯示我們剛才建立的DataTable。 您可以在 Products DataTable (ProductIDProductName 等) 中查看數據行清單,以及 ProductsTableAdapter (Fill () 和 GetProducts () ) 的方法。

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

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

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

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 Designer,在 ProductsTableAdapter 區段中按兩下滑鼠右鍵,然後選擇 [新增查詢]。

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

圖 14:Right-Click TableAdapter 和 Choose Add Query

我們會先提示您使用臨機操作 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 Designer 包含新的 TableAdapter 方法。

產品現在可以依類別查詢

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

請花點時間使用相同的技術,將 GetProductByProductID (productID) 方法。

您可以從 DataSet Designer 直接測試這些參數化查詢。 以滑鼠右鍵按下 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 傳遞至更新方法。 然後,這個方法會列舉傳入的 DataRow、判斷它們是否已透過 DataRow 的 RowState 屬性值 () 修改、新增或刪除,併發出每個記錄的適當資料庫要求。

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

圖 22:當叫用 Update 方法時,所有變更都會與資料庫同步處理, (按兩下即可檢視完整大小的映像)

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

這兩種數據修改模式都會使用 TableAdapter 的 InsertCommandUpdateCommandDeleteCommand 屬性,向資料庫發出其 INSERTUPDATEDELETE 命令。 您可以按兩下 DataSet Designer 中的 TableAdapter,然後移至 屬性視窗,以檢查和修改 InsertCommandUpdateCommand 和 DeleteCommand 屬性。 (請確定您已選取 TableAdapter,且 ProductsTableAdapter 對像是 屬性視窗.) 下拉式清單中選取的物件

TableAdapter 具有 InsertCommand、UpdateCommand 和 DeleteCommand 属性

圖 23:TableAdapter 具有 InsertCommandUpdateCommandDeleteCommand 屬性 (按兩下即可檢視完整大小的影像)

若要檢查或修改其中任何一個資料庫命令屬性,請按下 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 直接方法所建立的 () 方法可能有點麻煩,特別是針對具有許多數據行的數據表。 查看先前的程式代碼範例,如果沒有 IntelliSense 的協助,則不會特別清楚 Products 數據表數據行對應至每個輸入參數至 Update () Insert () 方法。 有時候,我們只想要更新單一數據行或兩個數據行,或想要自定義的 Insert () 方法,或許會傳回新插入記錄的 IDENTITY (自動遞增) 欄位的值。

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

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

圖 25:建立將新數據列新增至 Products 數據表的方法, (按兩下即可檢視完整大小的影像)

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

增強查詢以傳回SCOPE_IDENTITY () 值

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

最後,將新方法命名為 InsertProduct

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

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

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

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

將 ExecuteMode 屬性變更為純量

圖 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 方法一樣。 此外,如果您想要使用批次更新模式,您必須手動提供 InsertCommandUpdateCommand 和 DeleteCommand 屬性值。

新增剩餘的 TableAdapters

到目前為止,我們只會查看單一數據表的 TableAdapter 使用單一資料庫數據表。 不過,Northwind 資料庫包含數個相關數據表,我們需要在 Web 應用程式中使用。 具類型的數據集可以包含多個相關的 DataTable。 因此,若要完成 DAL,我們需要為其他數據表新增 DataTable,我們將在這些教學課程中使用。 若要將新的 TableAdapter 新增至具類型的數據集,請開啟 DataSet Designer,以滑鼠右鍵按兩下 Designer,然後選擇 [新增/ 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 之後的數據集 Designer

圖 31:新增四個 TableAdapters 之後的 DataSet Designer (按兩下即可檢視大小完整的影像)

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

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

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

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

在編譯時或在運行時間 (時,此架構資訊會在設計時間轉譯成 C# 或 Visual Basic 程式代碼,) ,此時您可以使用調試程式逐步執行。 若要檢視此自動產生的程式代碼,請移至 [類別檢視],然後向下切入至 TableAdapter 或 Typed DataSet 類別。 如果您沒有在畫面上看到 [類別檢視],請移至 [檢視] 功能表,然後從該處選取它,或按 Ctrl+Shift+C。 從 [類別檢視] 中,您可以看到 Typed 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 為基礎。 在下一個教學課程中,我們將定義一些商務規則,並瞭解如何在不同的商業規則層中實作規則。

快樂的程序設計!

深入閱讀

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

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

關於作者

Scott Mitchell 是七份 ASP/ASP.NET 書籍的作者,以及 1998 年以來與 Microsoft Web 技術合作的 4GuysFromRolla.com 作者。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格來連線到 ,您可以在 找到http://ScottOnWriting.NET

特別感謝

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