第五步:完成資料訪問層
注意,ProductsTableAdapters類從Products表中返回的 是CategoryID和SupplierID的值,但並不包括Categories表 的CategoryName欄位和Suppliers表的CompanyName欄位,儘管當 我們顯示產品資訊時,這些很可能是我們想要顯示的欄位。我們可以擴充TableAdapter的起始方 法GetProducts()來包含CategoryName和CompanyName欄位的值, 這方法進而會更新強型別的DataTable來包括這些新的欄位。
但這會造成一個問題,因為TableAdapter的插入,更新,刪除資料的方法是基於這個起始方法的,幸運的是, 自動產生的插入,更新,刪除方法並不會受SELECT子句中的子查詢的影響。如果我們注意把 對Categories和Suppliers的查詢添加成子查詢,而不是用JOIN語 句的話,我們可以避免重做這些修改資料的方法。在ProductsTableAdapter中的GetProducts()方法上按右滑鼠,選擇“配置”,然後,把SELECT子句改成:
SQL |
1 2 3 4 5 6 7 |
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 |
圖29: 更新GetProducts()方法的SELECT語句
在更新GetProducts()方法使用這個新查詢語句之後,對應的DataTable將包含2個新欄位,CategoryName和SupplierName。
圖30: Products DataTable多了2個新欄位
花點時間把GetProductsByCategoryID(categoryID)方法中的SELECT 子句也更新一下。
如果你使用JOIN句法更新GetProducts()中的SELECT語句的話 ,DataSet設計器不能使用DB直接模式自動產生插入,更新,以及刪除資料庫記錄的方法。你必須手工產生這 些方法,就象本教程早先時候我們對InsertProduct方法的做法一樣。此外,你必須手工提供 InsertCommand,UpdateCommand和DeleteCommand屬性值,假如你 想使用批更新模式的話。
添加其他的TableAdapter
到目前為止,我們只討論了針對單個資料表的單個TableAdapter。但是,Northwind資料庫裡含有我們需要在 我們的web應用中使用的幾個相關的表。一個強型別的DataSet可以包含多個相關的DataTable。因此,為了完 成我們的DAL,我們需要為這些我們將來要用到的資料表添加相應的DataTable。步驟如下,開啟 DataSet設計 器,在設計器上按右滑鼠,選擇“添加/TableAdapter”。這會產生一個新的DataTable和TableAdapter,然後我 們早先討論過的設定精靈會指引你完成配置。
花上幾分鐘,建立對應於下列查詢的TableAdapter及其方法。注意,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.ProductID) 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.ProductID) 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.ProductID)
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.ProductID) 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 ployeeID, LastName, FirstName,
Title, HireDate, ReportsTo, Country
FROM Employees
WHERE EmployeeID = @EmployeeID
圖31:添加了四個TableAdapter後的DataSet設計器
給DAL添加定製編碼
添加到強型別DataSet中的TableAdapter和DataTable是在一個XML Schema定義文 件(Northwind.xsd)中定義的。你可以在方案總管裡在Northwind.xsd 檔案上按右滑鼠,選擇“查看編碼(View Code)”,開啟這個Schema檔案來查看其中內容。
圖32:Northwinds強型別DataSet的XML Schema定義檔案
這個schema資訊在設計時編譯之後會被翻譯成C#或Visual Basic 編碼,或者如果有必要的話,會在運行時 翻譯,然後你就能在調試器裡單步遍曆執行。想查看這些自動產生的編碼的話,在類別檢視裡,展 開TableAdapter 類或者強型別的DataSet 類。如果在螢幕上看不到類別檢視的話,在“查看”(View)菜單裡選擇“ 類別檢視”,或者按鍵組合Ctrl+Shift+C。在類別檢視裡,你能看到強型別的DataSet類和TableAdapter類的屬性,方法和事件。想看某個特定的方法的編碼話,在類別檢視雙擊對應方法的名字或者在方法上按右滑鼠,選 擇“移至定義區(Go To Definition)”。
圖33:在類別檢視裡選擇“移至定義區(Go To Definition)”,查看自動產生的編碼
雖然自動產生的編碼省時省力,但這樣的編碼往往是非常通用化的(generic),為滿足一個應用程式特有的需 求需要做些定製。但擴充自動產生的編碼的風險在於,如果產生這些編碼的工具決定該是重建這些編碼的 時候了,則會把你定製的編碼衝掉。使用.NET 2.0中的一個新的部分(partial)類的概念,很容易將一個類的 定義分寫在幾個檔案裡。這允許我們給自動產生的類添加我們自己的方法,屬性,和事件,而不用擔心Visual Studio會衝掉我們的定製編碼。
為示範如何定製DAL起見,讓我們來給SuppliersRow 添加一個GetProducts()方法。這 個SuppliersRow類代表了Suppliers表的個別記錄,每個供應商(supplier)可以 提供0個到多個產品,所以GetProducts()將返回指定的供應商的這些產品。做法如 下,在App_Code檔案夾裡添加一個新的類檔案,將其命名為SuppliersRow.cs, 然後在其中添加下列編碼:
C# |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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); } } } |
這個部分(partial)類指示編譯器在編譯Northwind.SuppliersRow類時,應該包含我們剛定義的這個GetProducts()方法。如果你編譯你的項目,然後返回類別檢視,你就會看到GetProducts()已被列為Northwind.SuppliersRow的一個方法。
圖34: GetProducts()方法成為Northwind.SuppliersRow類的一部 分
GetProducts()方法現在就能用來枚舉一個指定供應商的產品列單,如下列編碼所示:
C# |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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>"); } |
This data can also be displayed in any of ASP.NET's data Web controls. The following page uses a GridView control with two fields:資料也可以在任何一種ASP.NET的Web控制項中顯示。下面這個網頁 使用了含有2個欄位的GridView 控制項:
- 一個BoundField用以顯示每個供應商的名字,
- 另一個TemplateField,包含了一個BulletedList控制項,用來綁定針對每個供應商調用 的GetProducts()方法返回的結果
我們將在以後的教程裡討論怎樣來顯示這樣的主/從(master-detail)報表。在這裡,這個例子的目的是用 來示範如何使用添加到Northwind.SuppliersRow類中的自訂的方法的。
SuppliersAndProducts.aspx
ASP.NET |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SuppliersAndProducts.aspx.cs" 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> <h1> Suppliers and Their Products</h1> <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
C# |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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: 供應商的公司名字列在左欄,他們的產品列在右欄
總結
構造web應用時,建立DAL應該是你最先做的步驟之一,應該在你開始建立表現層之前進行。使用Visual Studio的話,建立基於強型別DataSet的DAL是個可以不寫一行編碼,在10到15分鐘內就可完成的任務。以後的 教程將建立在這個DAL基礎之上。在下一個教程裡,我們將定義一堆商務規則,然後看一下如何在一個分開的 商務邏輯層裡實現這些規則。