為具類型資料集的 Tableadapter 建立新的預存程序 (C#)

作者:Scott Mitchell

下載 PDF

在先前的教學課程中,我們已在程式代碼中建立 SQL 語句,並將語句傳遞至要執行的資料庫。 替代方法是使用預存程式,其中 SQL 語句是在資料庫預先定義。 在本教學課程中,我們將瞭解如何讓 TableAdapter 精靈為我們產生新的預存程式。

簡介

這些教學課程的數據存取層 (DAL) 會使用具型別的數據集。 如 建立數據存取層 教學課程中所述,具型別數據集是由強型別的 DataTable 和 TableAdapters 所組成。 DataTable 代表系統中的邏輯實體,而 TableAdapters 介面與基礎資料庫執行數據存取工作。 這包括使用數據填入 DataTable、執行傳回純量數據的查詢,以及從資料庫插入、更新和刪除記錄。

TableAdapters 所執行的 SQL 命令可以是臨機操作 SQL 語句,例如 SELECT columnList FROM TableName或預存程式。 架構中的 TableAdapters 會使用臨機操作 SQL 語句。 不過,許多開發人員和資料庫管理員偏好預存程式,而非臨機操作 SQL 語句,以取得安全性、可維護性和可更新性的原因。 其他則偏好臨機操作 SQL 語句,以取得其彈性。 在自己的工作中,我偏好預存程式而非臨機操作 SQL 語句,但選擇使用臨機操作 SQL 語句來簡化先前的教學課程。

定義 TableAdapter 或新增方法時,TableAdapter 精靈可讓您輕鬆建立新的預存程式,或使用現有的預存程式,如同使用臨機操作 SQL 語句一樣。 在本教學課程中,我們將探討如何讓 TableAdapter 精靈自動產生預存程式。 在下一個教學課程中,我們將探討如何設定 TableAdapter 的 方法來使用現有或手動建立的預存程式。

注意

如需預存程式和臨機操作 SQL 的即時討論,請參閱 Rob Howard 的部落格文章「尚未使用預存程式?」和 Frans Bouma 部落格文章「預存程式不正確」、「M Kay?」。

預存程序基本概念

函式是所有程式設計語言通用的建構。 函式是呼叫函式時所執行的語句集合。 函式可以接受輸入參數,而且可以選擇性地傳回值。 預存程式 是資料庫建構,可與程式設計語言中的函式共用許多相似之處。 預存程式是由呼叫預存程式時執行的一組 T-SQL 語句所組成。 預存程式可能會接受零到許多輸入參數,而且可以傳回純量值、輸出參數,或最常從 SELECT 查詢傳回結果集。

注意

預存程式通常稱為 sprocs 或 SPS 。

預存程式是使用 CREATE PROCEDURE T-SQL 語句所建立。 例如,下列 T-SQL 腳本會建立名為 的預存程式,該預存程式會採用名為 GetProductsByCategoryID@CategoryID 的單一參數,並傳回ProductID數據表中具有相符CategoryID值之數據行的 ProductsProductNameUnitPrice、 和 Discontinued 字段:

CREATE PROCEDURE GetProductsByCategoryID
(
    @CategoryID int
)
AS
SELECT ProductID, ProductName, UnitPrice, Discontinued
FROM Products
WHERE CategoryID = @CategoryID

建立此預存程序之後,就可以使用下列語法呼叫它:

EXEC GetProductsByCategory categoryID

注意

在下一個教學課程中,我們將檢查透過Visual Studio IDE建立預存程式。 不過,在本教學課程中,我們將讓 TableAdapter 精靈自動為我們產生預存程式。

除了只傳回數據之外,預存程式通常用來在單一交易的範圍內執行多個資料庫命令。 例如,名為 DeleteCategory的預存程式可能會採用 @CategoryID 參數並執行兩 DELETE 個語句:首先,一個刪除相關產品,另一個刪除指定的類別。 預存程式內的多個語句 不會 自動包裝在交易內。 必須發出額外的 T-SQL 命令,以確保預存程式將多個命令視為不可部分完成的作業。 我們將在後續教學課程中,瞭解如何在交易範圍內包裝預存程式命令。

在架構中使用預存程式時,數據存取層的方法會叫用特定的預存程式,而不是發出臨機操作 SQL 語句。 這會將在資料庫上執行 (的 SQL 語句位置集中) ,而不是在應用程式架構中定義它。 這個集中化可讓查詢更容易尋找、分析和調整,並提供更清楚的了解,了解資料庫使用的位置和方式。

如需預存程式基本概念的詳細資訊,請參閱本教學課程結尾的一節中的資源。

步驟 1:建立進階數據存取層案例網頁

在開始討論如何使用預存程式建立 DAL 之前,讓我們先花一點時間在網站專案中建立 ASP.NET 頁面,而我們需要此課程和接下來的幾個教學課程。 首先,新增名為 AdvancedDAL的新資料夾。 接下來,將下列 ASP.NET 頁面新增至該資料夾,請務必將每個頁面與 Site.master 主版頁面產生關聯:

  • Default.aspx
  • NewSprocs.aspx
  • ExistingSprocs.aspx
  • JOINs.aspx
  • AddingColumns.aspx
  • ComputedColumns.aspx
  • EncryptingConfigSections.aspx
  • ManagedFunctionsAndSprocs.aspx

新增進階數據存取層案例的 ASP.NET 頁面教學課程

圖 1:新增進階數據存取層案例的 ASP.NET 頁面教學課程

就像在其他資料夾中一樣, Default.aspx 資料夾中 AdvancedDAL 會列出其區段中的教學課程。 回想一下, SectionLevelTutorialListing.ascx 使用者控件會提供這項功能。 因此,請將此使用者控制件Default.aspx從 方案總管 拖曳至頁面的設計檢視,將它新增至 。

將 SectionLevelTutorialListing.ascx 使用者控件新增至 Default.aspx

圖 2:將使用者控件新增 SectionLevelTutorialListing.ascxDefault.aspx (按兩下即可檢視完整大小的影像)

最後,將這些頁面新增為檔案的專案 Web.sitemap 。 具體而言,在使用 Batched Data <siteMapNode>之後新增下列標記:

<siteMapNode url="~/AdvancedDAL/Default.aspx" 
    title="Advanced DAL Scenarios" 
    description="Explore a number of advanced Data Access Layer scenarios.">
    
    <siteMapNode url="~/AdvancedDAL/NewSprocs.aspx" 
        title="Creating New Stored Procedures for TableAdapters" 
        description="Learn how to have the TableAdapter wizard automatically 
            create and use stored procedures." />
    <siteMapNode url="~/AdvancedDAL/ExistingSprocs.aspx" 
        title="Using Existing Stored Procedures for TableAdapters" 
        description="See how to plug existing stored procedures into a 
            TableAdapter." />
    <siteMapNode url="~/AdvancedDAL/JOINs.aspx" 
        title="Returning Data Using JOINs" 
        description="Learn how to augment your DataTables to work with data 
            returned from multiple tables via a JOIN query." />
    <siteMapNode url="~/AdvancedDAL/AddingColumns.aspx" 
        title="Adding DataColumns to a DataTable" 
        description="Master adding new columns to an existing DataTable." />
    <siteMapNode url="~/AdvancedDAL/ComputedColumns.aspx" 
        title="Working with Computed Columns" 
        description="Explore how to work with computed columns when using 
            Typed DataSets." />
    <siteMapNode url="~/AdvancedDAL/EncryptingConfigSections.aspx" 
        title="Protected Connection Strings in Web.config" 
        description="Protect your connection string information in 
            Web.config using encryption." />
    <siteMapNode url="~/AdvancedDAL/ManagedFunctionsAndSprocs.aspx" 
        title="Creating Managed SQL Functions and Stored Procedures" 
        description="See how to create SQL functions and stored procedures 
            using managed code." />
</siteMapNode>

更新 Web.sitemap之後,請花點時間透過瀏覽器檢視教學課程網站。 左側功能表現在包含進階 DAL 案例教學課程的專案。

網站地圖現在包含進階 DAL 案例教學課程的專案

圖 3:網站地圖現在包含進階 DAL 案例教學課程的專案

步驟 2:設定 TableAdapter 以建立新的預存程式

為了示範如何建立使用預存程式而非臨機操作 SQL 語句的數據存取層,讓我們在 ~/App_Code/DAL 名為 的 NorthwindWithSprocs.xsd資料夾中建立新的具型別數據集。 由於我們已在先前的教學課程中詳細說明此程式,因此我們將快速完成這裡的步驟。 如果您遇到停滯或需要建立和設定具型別數據集的進一步逐步指示,請參閱 建立數據存取層 教學課程。

以滑鼠右鍵按兩下 DAL 資料夾,選擇 [新增專案],然後選取 [資料集] 範本,以將新的 DataSet 新增至專案,如圖 4 所示。

將新的具類型的數據集新增至名為 NorthwindWithSprocs.xsd 的專案

圖 4:將新的具型別數據集新增至名為 NorthwindWithSprocs.xsd 的專案 (按兩下即可檢視完整大小的影像)

這會建立新的 Typed DataSet、開啟其 Designer、建立新的 TableAdapter,然後啟動 TableAdapter 組態精靈。 TableAdapter 設定精靈的第一個步驟會要求我們選取要使用的資料庫。 Northwind 資料庫的 連接字串 應該列在下拉式清單中。 選取此選項,然後按 [下一步]。

在下一個畫面中,我們可以選擇 TableAdapter 應該如何存取資料庫。 在先前的教學課程中,我們選取了第一個選項使用 SQL 語句。 在本教學課程中,選取第二個選項[建立新的預存程式],然後按 [下一步]。

指示 TableAdapter 建立新的預存程式

圖 5:指示 TableAdapter 建立新的預存程式 (按兩下以檢視大小完整的映像)

就像使用臨機操作 SQL 語句一樣,在下列步驟中,我們會要求提供 SELECT TableAdapter 主要查詢的 語句。 但是,除了使用這裡輸入的 SELECT 語句直接執行臨機操作查詢,TableAdapter 精靈會建立包含此 SELECT 查詢的預存程式。

使用此 TableAdapter 的下列 SELECT 查詢:

SELECT ProductID, ProductName, SupplierID, CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued
FROM Products

輸入 SELECT 查詢

圖 6:輸入 SELECT 查詢 (按鍵即可檢視大小完整的影像)

注意

上述查詢與 Typed DataSet 中 Northwind 的主要查詢ProductsTableAdapter稍有不同。 回想一下,Typed DataSet 中的 Northwind 包含ProductsTableAdapter兩個相互關聯的子查詢,以傳回每個產品類別和供應商的類別名稱和公司名稱。 在即將推出的更新 TableAdapter 以使用 JOINs 教學課程中,我們將探討如何將此相關數據新增至此 TableAdapter。

請花點時間按兩下 [進階選項] 按鈕。 從這裡,我們可以指定精靈是否也應該為 TableAdapter 產生插入、更新和刪除語句、是否要使用開放式並行存取,以及插入和更新之後是否應該重新整理數據表。 預設會檢查 [產生插入]、[更新] 和 [刪除語句] 選項。 將它保持核取狀態。 在本教學課程中,請取消核取 [使用開放式並行存取選項]。

當 TableAdapter 精靈自動建立預存程式時,它似乎會忽略 [重新整理數據表] 選項。 不論是否已核取此複選框,產生的插入和更新預存程式都會擷取 Just-inserted 或 Just-updated 記錄,如我們在步驟 3 中看到。

保留 [產生插入]、[更新] 和 [刪除語句] 選項已核取

圖 7:保留 [產生插入]、[更新] 和 [刪除語句] 選項已核取

注意

如果已核取 [使用開放式並行存取] 選項,精靈會將其他條件新增至 子句, WHERE 以防止其他字段有變更時更新數據。 如需使用 TableAdapter 內建開放式並行控制功能的詳細資訊,請參閱 實作開放式 並行存取教學課程。

輸入 SELECT 查詢並確認已核取 [產生插入]、[更新] 和 [刪除語句] 選項之後,請按 [下一步]。 下圖 8 所示的下一個畫面會提示精靈建立的預存程式名稱,以便選取、插入、更新和刪除數據。 將這些預存程式名稱變更為 Products_SelectProducts_InsertProducts_UpdateProducts_Delete

重新命名預存程式

圖 8:將預存程式重新命名 (按下即可檢視完整大小的映像)

若要查看 T-SQL TableAdapter 精靈將用來建立四個預存程式,請按兩下 [預覽 SQL 腳稿] 按鈕。 從 [預覽 SQL 腳本] 對話框,您可以將腳本儲存至檔案,或將其複製到剪貼簿。

預覽用來產生預存程式的 SQL 腳本

圖 9:預覽用來產生預存程式的 SQL 腳本

命名預存程序之後,按 [下一步] 將 TableAdapter 命名為對應的方法。 就像使用臨機操作 SQL 語句一樣,我們可以建立填滿現有 DataTable 或傳回新語句的方法。 我們也可以指定 TableAdapter 是否應該包含插入、更新和刪除記錄的 DB-Direct 模式。 保留所有三個複選框,但重新命名 Return a DataTable 方法以 GetProducts (,如圖 10) 所示。

將方法命名為 Fill 和 GetProducts

圖 10:命名方法和 FillGetProducts (按兩下即可檢視大小完整的影像)

按 [下一步] 以查看精靈將執行的步驟摘要。 按兩下 [完成] 按鈕來完成精靈。 精靈完成後,您將會返回 DataSet s Designer,現在應該包含 ProductsDataTable

DataSet s Designer 顯示新新增的產品DataTable

圖 11:D ataSet s Designer 顯示新新增ProductsDataTable的 (按兩下即可檢視完整大小的影像)

步驟 3:檢查新建立的預存程式

步驟 2 中使用的 TableAdapter 精靈會自動建立預存程式,以選取、插入、更新和刪除數據。 您可以移至 [伺服器總管] 並向下切入資料庫 [預存程式] 資料夾,透過Visual Studio 檢視或修改這些預存程式。 如圖 12 所示,Northwind 資料庫包含四個新的預存程式: Products_DeleteProducts_InsertProducts_SelectProducts_Update

您可以在資料庫預存程式資料夾中找到步驟 2 中建立的四個預存程式

圖 12:您可以在資料庫預存程式資料夾中找到步驟 2 中建立的四個預存程式

注意

如果您沒有看到 [伺服器總管],請移至 [檢視] 功能表,然後選擇 [伺服器總管] 選項。 如果您沒有看到從步驟 2 新增的產品相關預存程式,請嘗試以滑鼠右鍵按兩下 [預存程式] 資料夾,然後選擇 [重新整理]。

若要檢視或修改預存程式,請在 [伺服器總管] 中按兩下其名稱,或者,以滑鼠右鍵按兩下預存程式,然後選擇 [開啟]。 圖 13 顯示 Products_Delete 開啟時的預存程式。

您可以從 Visual Studio 內開啟和修改預存程式

圖 13:您可以從 Visual Studio 內開啟和修改預存程式, (按兩下即可檢視完整大小的映像)

Products_Select 預存程式的內容Products_Delete相當簡單。 Products_Insert另一方面,和 Products_Update 預存程式需要更仔細的檢查,因為它們會在和 INSERTUPDATE 語句之後執行 SELECT 語句。 例如,下列 SQL 構成 Products_Insert 預存程式:

ALTER PROCEDURE dbo.Products_Insert
(
    @ProductName nvarchar(40),
    @SupplierID int,
    @CategoryID int,
    @QuantityPerUnit nvarchar(20),
    @UnitPrice money,
    @UnitsInStock smallint,
    @UnitsOnOrder smallint,
    @ReorderLevel smallint,
    @Discontinued bit
)
AS
    SET NOCOUNT OFF;
INSERT INTO [Products] ([ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], 
    [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) 
VALUES (@ProductName, @SupplierID, @CategoryID, @QuantityPerUnit, @UnitPrice, 
    @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued);
    
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, 
    UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued 
FROM Products 
WHERE (ProductID = SCOPE_IDENTITY())

預存程式接受輸入參數, Products 這些數據行是由 TableAdapter 精靈中指定的查詢所 SELECT 傳回,而且這些值用於 語句中 INSERTINSERT在語句之後,SELECT查詢可用來傳回Products數據行值, (包括ProductID新加入記錄的) 。 使用 Batch Update 模式新增記錄時,這項重新整理功能很有用,因為它會自動使用資料庫指派的自動遞增值來更新新加入 ProductRow 的實例 ProductID 屬性。

下列程式代碼說明這項功能。 包含並ProductsTableAdapterProductsDataTable針對NorthwindWithSprocs具型別資料集建立的 。 藉由建立 ProductsRow 實例、提供其值,以及呼叫 TableAdapter s Update 方法,傳入 ProductsDataTable,以新增產品至資料庫。 在內部,TableAdapter 的 Update 方法會列舉 ProductsRow 在此範例中傳入的 DataTable (中的實例, 只有一個 - 我們剛新增) ,並執行適當的插入、更新或刪除命令。 在此情況下,會 Products_Insert 執行預存程式,將新記錄 Products 加入數據表,並傳回新加入記錄的詳細數據。 接著會 ProductsRow 更新實例的值 ProductIDUpdate方法完成之後,我們可以透過 ProductsRow s ProductID 屬性存取新加入的記錄值ProductID

// Create the ProductsTableAdapter and ProductsDataTable
NorthwindWithSprocsTableAdapters.ProductsTableAdapter productsAPI = 
    new NorthwindWithSprocsTableAdapters.ProductsTableAdapter();
NorthwindWithSprocs.ProductsDataTable products = 
    new NorthwindWithSprocs.ProductsDataTable();
// Create a new ProductsRow instance and set its properties
NorthwindWithSprocs.ProductsRow product = products.NewProductsRow();
product.ProductName = "New Product";
product.CategoryID = 1;  // Beverages
product.Discontinued = false;
// Add the ProductsRow instance to the DataTable
products.AddProductsRow(product);
// Update the DataTable using the Batch Update pattern
productsAPI.Update(products);
// At this point, we can determine the value of the newly-added record's ProductID
int newlyAddedProductIDValue = product.ProductID;

Products_Update存程式同樣會在其語句後面UPDATE包含 SELECT 語句。

ALTER PROCEDURE dbo.Products_Update
(
    @ProductName nvarchar(40),
    @SupplierID int,
    @CategoryID int,
    @QuantityPerUnit nvarchar(20),
    @UnitPrice money,
    @UnitsInStock smallint,
    @UnitsOnOrder smallint,
    @ReorderLevel smallint,
    @Discontinued bit,
    @Original_ProductID int,
    @ProductID int
)
AS
    SET NOCOUNT OFF;
UPDATE [Products] 
SET [ProductName] = @ProductName, [SupplierID] = @SupplierID, 
    [CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit, 
    [UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock, 
    [UnitsOnOrder] = @UnitsOnOrder, [ReorderLevel] = @ReorderLevel, 
    [Discontinued] = @Discontinued 
WHERE (([ProductID] = @Original_ProductID));
    
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, 
    UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued 
FROM Products 
WHERE (ProductID = @ProductID)

請注意,這個預存程式包含的兩個輸入參數 ProductID@Original_ProductID@ProductID。 這項功能允許變更主鍵的案例。 例如,在員工資料庫中,每個員工記錄可能會使用員工的社會安全號碼作為其主鍵。 若要變更現有的員工社會安全號碼,必須提供新的社會安全號碼和原始號碼。 Products對於數據表,不需要這類功能,因為數據ProductID行是數據IDENTITY行,而且絕對不應該變更。 事實上, UPDATE 預存程式中的 Products_Update 語句不會在其數據行清單中包含數據 ProductID 行。 因此,雖然 @Original_ProductID 在語句 s WHERE 子句中使用UPDATE,但數據表是多餘的Products,而且可由 參數取代@ProductID。 修改預存程式的參數時,TableAdapter 方法 (使用該預存程式的) 也一定要更新。

步驟 4:修改預存程序的參數並更新 TableAdapter

@Original_ProductID因為 參數是多餘的,所以讓我們完全從Products_Update預存程式中移除它。 開啟預Products_Update存程式、刪除 @Original_ProductID 參數,然後在 語句的 UPDATE 子句中WHERE,將所使用的參數名稱從 @Original_ProductID 變更為 @ProductID。 進行這些變更之後,預存程式內的 T-SQL 看起來應該如下所示:

ALTER PROCEDURE dbo.Products_Update
(
    @ProductName nvarchar(40),
    @SupplierID int,
    @CategoryID int,
    @QuantityPerUnit nvarchar(20),
    @UnitPrice money,
    @UnitsInStock smallint,
    @UnitsOnOrder smallint,
    @ReorderLevel smallint,
    @Discontinued bit,
    @ProductID int
)
AS
    SET NOCOUNT OFF;
UPDATE [Products] SET [ProductName] = @ProductName, [SupplierID] = @SupplierID, 
    [CategoryID] = @CategoryID, [QuantityPerUnit] = @QuantityPerUnit, 
    [UnitPrice] = @UnitPrice, [UnitsInStock] = @UnitsInStock, 
    [UnitsOnOrder] = @UnitsOnOrder, [ReorderLevel] = @ReorderLevel, 
    [Discontinued] = @Discontinued 
WHERE (([ProductID] = @ProductID));
    
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, 
    UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued 
FROM Products 
WHERE (ProductID = @ProductID)

若要將這些變更儲存至資料庫,請按兩下工具列中的 [儲存] 圖示,或按 Ctrl+S。 此時, Products_Update 預存程式不需要 @Original_ProductID 輸入參數,但 TableAdapter 已設定為傳遞這類參數。 您可以查看 TableAdapter 將傳送至Products_Update預存程式的參數,方法是選取 DataSet Designer 中的 TableAdapter、移至 屬性視窗,然後按下集合Parameters中的UpdateCommand省略號。 這會顯示 [參數集合 編輯器] 對話框,如圖 14 所示。

Parameters 集合 編輯器 清單 傳遞至Products_Update預存程式的參數

圖 14:參數集合 編輯器 清單 傳遞至Products_Update預存程式的參數

只要從成員清單中選取 @Original_ProductID 參數,然後按兩下 [移除] 按鈕,即可從這裡移除此參數。

或者,您可以用滑鼠右鍵按兩下 Designer中的 TableAdapter 並選擇 [設定],以重新整理所有方法所使用的參數。 這會啟動 TableAdapter 組態精靈,列出用來選取、插入、更新和刪除的預存程式,以及預存程式預期接收的參數。 如果您按兩下 [更新] 下拉式清單,您可以看到 Products_Update 預存程式預期的輸入參數,現在不再包含 @Original_ProductID (請參閱圖 15) 。 只要按下 [完成] 即可自動更新 TableAdapter 所使用的參數集合。

您也可以使用 TableAdapter 的設定精靈來重新整理其方法參數集合

圖 15:您也可以使用 TableAdapter 的設定精靈來重新整理其方法參數集合, (按兩下即可檢視大小完整的映像)

步驟 5:新增其他 TableAdapter 方法

如步驟 2 所述,建立新的 TableAdapter 時,很容易自動產生對應的預存程式。 將其他方法新增至 TableAdapter 時,也是如此。 為了說明這一點,讓我們將方法新增 GetProductByProductID(productID)ProductsTableAdapter 步驟 2 中建立的 。 這個方法會接受作為輸入 ProductID 值,並傳回指定產品的詳細數據。

從以滑鼠右鍵按下 TableAdapter,然後從操作功能表中選擇 [新增查詢] 開始。

將新查詢新增至 TableAdapter

圖 16:將新查詢新增至 TableAdapter

這會啟動 TableAdapter 查詢設定精靈,這會先提示 TableAdapter 如何存取資料庫。 若要建立新的預存程式,請選擇 [建立新的預存程式] 選項,然後按 [下一步]。

選擇 [建立新的預存程式] 選項

圖 17:選擇 [建立新的預存程式] 選項 (按兩下以檢視大小完整的映像)

下一個畫面會要求我們識別要執行的查詢類型,無論是傳回一組數據列或單一純量值,還是執行 UPDATEINSERTDELETE 語句。 GetProductByProductID(productID)由於方法會傳回數據列,因此請保留 SELECT 以傳回選取的數據列選項,然後按 [下一步]。

選擇會傳回數據列選項的SELECT

圖 18:選擇 SELECT 以傳回數據列選項 (按一下以檢視大小完整的影像)

下一個畫面會顯示 TableAdapter 的主要查詢,其只會列出預存程式的名稱 (dbo.Products_Select) 。 將預存程式名稱取代為下列 SELECT 語句,這會傳回指定產品的所有產品欄位:

SELECT ProductID, ProductName, SupplierID, CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued
FROM Products
WHERE ProductID = @ProductID

以 SELECT 查詢取代預存程式名稱

圖 19:將預存程式名稱取代為 SELECT [查詢] (按兩下即可檢視大小完整的映像)

後續畫面會要求您命名將要建立的預存程式。 輸入名稱 Products_SelectByProductID ,然後按 [下一步]。

將新預存程式命名Products_SelectByProductID

圖 20:將新預存程式 Products_SelectByProductID 命名 (按兩下以檢視大小完整的映像)

精靈的最後一個步驟可讓我們變更產生的方法名稱,以及指出是否要使用填滿 DataTable 模式、傳回 DataTable 模式,或兩者。 針對這個方法,請將這兩個選項保持核取狀態,但將方法重新命名為 FillByProductIDGetProductByProductID。 按 [下一步] 以檢視精靈將執行的步驟摘要,然後按兩下 [完成] 以完成精靈。

將 TableAdapter 的方法重新命名為 FillByProductID 和 GetProductByProductID

圖 21:將 TableAdapter 的方法重新命名為 FillByProductID , (GetProductByProductID按兩下即可檢視完整大小的影像)

完成精靈之後,TableAdapter 有可用的新方法, GetProductByProductID(productID) 叫用時,將會執行剛建立的 Products_SelectByProductID 預存程式。 請花點時間從 [伺服器總管] 檢視這個新的預存程式,方法是鑽研 [預存程式] 資料夾,然後開啟 Products_SelectByProductID (如果您看不到,請以滑鼠右鍵按兩下 [預存程式] 資料夾,然後選擇 [重新整理) ]。

請注意, SelectByProductID 預存程式會採用 @ProductID 作為輸入參數,並執行 SELECT 我們在精靈中輸入的語句。

ALTER PROCEDURE dbo.Products_SelectByProductID
(
    @ProductID int
)
AS
    SET NOCOUNT ON;
SELECT ProductID, ProductName, SupplierID, CategoryID, 
       QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, 
       ReorderLevel, Discontinued
FROM Products
WHERE ProductID = @ProductID

步驟 6:建立商業規則層類別

在整個教學課程系列中,我們努力維護分層式架構,其中表示層會呼叫商業規則層 (BLL) 。 為了遵守此設計決策,我們必須先為新的具型別數據集建立 BLL 類別,才能從呈現層存取產品數據。

在資料夾中建立名為 ProductsBLLWithSprocs.cs~/App_Code/BLL 的新類別檔案,並將其新增至下列程式代碼:

using System;
using System.Data;
using System.Configuration;
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 NorthwindWithSprocsTableAdapters;
[System.ComponentModel.DataObject]
public class ProductsBLLWithSprocs
{
    private ProductsTableAdapter _productsAdapter = null;
    protected ProductsTableAdapter Adapter
    {
        get
        {
            if (_productsAdapter == null)
                _productsAdapter = new ProductsTableAdapter();
            return _productsAdapter;
        }
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
    public NorthwindWithSprocs.ProductsDataTable GetProducts()
    {
        return Adapter.GetProducts();
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public NorthwindWithSprocs.ProductsDataTable GetProductByProductID(int productID)
    {
        return Adapter.GetProductByProductID(productID);
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Insert, true)]
    public bool AddProduct
        (string productName, int? supplierID, int? categoryID, 
         string quantityPerUnit, decimal? unitPrice, short? unitsInStock, 
         short? unitsOnOrder, short? reorderLevel, bool discontinued)
    {
        // Create a new ProductRow instance
        NorthwindWithSprocs.ProductsDataTable products = 
            new NorthwindWithSprocs.ProductsDataTable();
        NorthwindWithSprocs.ProductsRow product = products.NewProductsRow();
        product.ProductName = productName;
        if (supplierID == null) 
            product.SetSupplierIDNull(); 
        else 
            product.SupplierID = supplierID.Value;
        if (categoryID == null) 
            product.SetCategoryIDNull(); 
        else 
            product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) 
            product.SetQuantityPerUnitNull(); 
        else 
            product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) 
            product.SetUnitPriceNull(); 
        else 
            product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) 
            product.SetUnitsInStockNull(); 
        else 
            product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) 
            product.SetUnitsOnOrderNull(); 
        else 
            product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null)
            product.SetReorderLevelNull(); 
        else 
            product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;
        // Add the new product
        products.AddProductsRow(product);
        int rowsAffected = Adapter.Update(products);
        // Return true if precisely one row was inserted, otherwise false
        return rowsAffected == 1;
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Update, true)]
    public bool UpdateProduct
        (string productName, int? supplierID, int? categoryID, string quantityPerUnit,
        decimal? unitPrice, short? unitsInStock, short? unitsOnOrder, 
        short? reorderLevel, bool discontinued, int productID)
    {
        NorthwindWithSprocs.ProductsDataTable products = 
            Adapter.GetProductByProductID(productID);
        if (products.Count == 0)
            // no matching record found, return false
            return false;
        NorthwindWithSprocs.ProductsRow product = products[0];
        product.ProductName = productName;
        if (supplierID == null) 
            product.SetSupplierIDNull(); 
        else 
            product.SupplierID = supplierID.Value;
        if (categoryID == null) 
            product.SetCategoryIDNull(); 
        else 
            product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) 
            product.SetQuantityPerUnitNull(); 
        else 
            product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) 
            product.SetUnitPriceNull(); 
        else 
            product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) 
            product.SetUnitsInStockNull(); 
        else 
            product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) 
            product.SetUnitsOnOrderNull(); 
        else 
            product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) 
            product.SetReorderLevelNull(); 
        else 
            product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;
        // Update the product record
        int rowsAffected = Adapter.Update(product);
        // Return true if precisely one row was updated, otherwise false
        return rowsAffected == 1;
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteProduct(int productID)
    {
        int rowsAffected = Adapter.Delete(productID);
        // Return true if precisely one row was deleted, otherwise false
        return rowsAffected == 1;
    }
}

這個類別會ProductsBLL模擬先前教學課程中的類別語意,但會使用 ProductsTableAdapter DataSet 中的 NorthwindWithSprocsProductsDataTable 物件。 例如,類別不會像一樣ProductsBLL地在using NorthwindTableAdapters類別檔案開頭有 語句,而是ProductsBLLWithSprocs使用 using NorthwindWithSprocsTableAdapters。 同樣地, ProductsDataTable 這個類別中使用的和 ProductsRow 物件前面會加上 NorthwindWithSprocs 命名空間。 類別 ProductsBLLWithSprocs 提供兩種數據存取方法, GetProducts 以及和 GetProductByProductID方法來新增、更新和刪除單一產品實例。

步驟 7:使用NorthwindWithSprocs呈現層中的數據集

此時,我們已建立 DAL,其會使用預存程式來存取和修改基礎資料庫數據。 我們也建置了一個基本的 BLL,其中包含擷取所有產品或特定產品的方法,以及新增、更新和刪除產品的方法。 若要捨入本教學課程,讓我們建立使用 BLL 類別來顯示、更新和刪除記錄的 ProductsBLLWithSprocs ASP.NET 頁面。

NewSprocs.aspx開啟 資料夾中的頁面AdvancedDAL,並將 GridView 從 [工具箱] 拖曳至 Designer,並將其命名為 Products。 從 GridView 智慧標記選擇將它系結至名為 ProductsDataSource的新 ObjectDataSource。 將 ObjectDataSource 設定為使用 ProductsBLLWithSprocs 類別,如圖 22 所示。

將 ObjectDataSource 設定為使用 ProductsBLLWithSprocs 類別

圖 22:將 ObjectDataSource 設定為使用 ProductsBLLWithSprocs 類別 (按兩下即可檢視完整大小的影像)

SELECT 索引標籤標的下拉式清單有兩個選項, GetProducts 以及 GetProductByProductID。 由於我們想要在 GridView 中顯示所有產品,請選擇 GetProducts 方法。 UPDATE、INSERT 和 DELETE 索引標籤中的下拉式清單各只有一個方法。 請確定每個下拉式清單都已選取適當的方法,然後按兩下 [完成]。

在 ObjectDataSource 精靈完成之後,Visual Studio 會將 BoundFields 和 CheckBoxField 新增至 GridView 中的產品數據欄位。 檢查智慧標記中存在的 [啟用編輯] 和 [啟用刪除] 選項,以開啟 GridView 的內建編輯和刪除功能。

頁面包含已啟用編輯和刪除支援的 GridView

圖 23:頁面包含已啟用編輯和刪除支援的 GridView (按兩下即可檢視大小完整的影像)

如先前教學課程中所討論,在 ObjectDataSource 精靈完成時,Visual Studio 會將 OldValuesParameterFormatString 屬性設定為 original_{0}。 這必須還原為的預設值 {0} ,數據修改功能才能在 BLL 中方法預期的參數正常運作。 因此,請務必將 屬性設定 OldValuesParameterFormatString 為 {0} ,或從宣告式語法完全移除屬性。

完成 [設定數據源精靈] 之後,開啟 GridView 中的編輯和刪除支援,並將 ObjectDataSource s OldValuesParameterFormatString 屬性傳回其預設值,您的頁面宣告式標記看起來應該如下所示:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="ProductName" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" 
            SortExpression="SupplierID" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit" 
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" 
            SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock" 
            SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder" 
            SortExpression="UnitsOnOrder" />
        <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel" 
            SortExpression="ReorderLevel" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" 
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    DeleteMethod="DeleteProduct" InsertMethod="AddProduct" 
    SelectMethod="GetProducts" TypeName="ProductsBLLWithSprocs" 
    UpdateMethod="UpdateProduct">
    <DeleteParameters>
        <asp:Parameter Name="productID" Type="Int32" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="unitsInStock" Type="Int16" />
        <asp:Parameter Name="unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="reorderLevel" Type="Int16" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
    <InsertParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="supplierID" Type="Int32" />
        <asp:Parameter Name="categoryID" Type="Int32" />
        <asp:Parameter Name="quantityPerUnit" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="unitsInStock" Type="Int16" />
        <asp:Parameter Name="unitsOnOrder" Type="Int16" />
        <asp:Parameter Name="reorderLevel" Type="Int16" />
        <asp:Parameter Name="discontinued" Type="Boolean" />
    </InsertParameters>
</asp:ObjectDataSource>

此時,我們可以自定義編輯介面以包含驗證、讓 CategoryIDSupplierID 數據行轉譯為DropDownLists等等,來整理 GridView。 我們也可以將用戶端確認新增至 [刪除] 按鈕,我們鼓勵您花點時間實作這些增強功能。 不過,由於這些主題已在先前的教學課程中討論過,因此我們不會在這裡再次討論這些主題。

不論您是否要增強 GridView,在瀏覽器中測試頁面的核心功能。 如圖 24 所示,頁面會列出 GridView 中提供個別數據列編輯和刪除功能的產品。

您可以從 GridView 檢視、編輯和刪除產品

圖 24:您可以從 GridView 檢視、編輯和刪除產品, (按兩下即可檢視完整大小的影像)

摘要

具型別數據集中的 TableAdapters 可以使用臨機操作 SQL 語句或預存程式,從數據庫存取數據。 使用預存程式時,可以使用現有的預存程式,也可以指示 TableAdapter 精靈根據 SELECT 查詢建立新的預存程式。 在本教學課程中,我們探索了如何為我們自動建立預存程式。

雖然自動產生預存程式有助於節省時間,但在某些情況下,精靈所建立的預存程式不符合我們自行建立的專案。 其中一個範例是Products_Update預存程式,其@Original_ProductID預期@Original_ProductID@ProductID輸入參數都是多餘的。

在許多情況下,預存程式可能已經建立,或我們想要手動建置預存程式,以便更精細地控制預存程式的命令。 不論是哪一種情況,我們都會想要指示 TableAdapter 針對其方法使用現有的預存程式。 我們將在下一個教學課程中瞭解如何完成此作業。

快樂的程序設計!

深入閱讀

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

關於作者

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

特別感謝

本教學課程系列是由許多實用的檢閱者所檢閱。 本教學課程的首席檢閱者是一位首席審核者。 想要檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行放在 mitchell@4GuysFromRolla.com。