更新 TableAdapter 以使用 JOIN (C#)
使用資料庫時,通常會要求分散於多個資料表的資料。 若要從兩個不同的資料表擷取資料,我們可以使用相互關聯的子查詢或 JOIN 作業。 在本教學課程中,我們會先比較相互關聯的子查詢和 JOIN 語法,再探討如何建立 TableAdapter,在其主查詢中包含 JOIN。
簡介
使用關聯式資料庫時,我們感興趣的資料通常分散在多個資料表。 例如,當顯示產品資訊時,我們可能會想要列出每個產品相對應的類別和供應商名稱。 Products
資料表具有 CategoryID
和 SupplierID
值,但實際的類別和供應商名稱分別位於 Categories
和 Suppliers
資料表中。
若要從另一個相關資料表擷取資訊,我們可以使用「相互關聯的子查詢」或 JOIN
。 相互關聯的子查詢是巢狀 SELECT
查詢,會參考外部查詢中的資料行。 例如,在建立資料存取層教學課程中,我們使用了兩個相互關聯的子查詢在 ProductsTableAdapter
的主查詢中傳回每個產品的類別和供應商名稱。 JOIN
是一種 SQL 建構函式,可合併兩個不同資料表的相關資料列。 我們在使用 SqlDataSource 控制項查詢資料教學課程使用了 JOIN
,在每個產品一旁顯示類別資訊。
我們避開搭配 TableAdapter 使用 JOIN
的原因是,TableAdapter 精靈中對自動產生相對應的 INSERT
、UPDATE
和 DELETE
陳述式有所限制。 更具體來說,如果 TableAdapter 的主查詢包含任何 JOIN
,TableAdapter 就無法自動為其 InsertCommand
、UpdateCommand
和 DeleteCommand
屬性建立臨機操作 SQL 陳述式或預存程序。
在本教學課程中,我們會先簡短比較並對照相互關聯的子查詢和 JOIN
,再探索如何建立 TableAdapter,在其主查詢中包含 JOIN
。
比較並對照相互關聯的子查詢和 JOIN
回想一下,在第一個教學課程中的 Northwind
DataSet 中建立的 ProductsTableAdapter
使用相互關聯的子查詢來傳回每個產品的相對應類別和供應商名稱。 ProductsTableAdapter
的主查詢如下所示。
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
兩個相互關聯的子查詢 - (SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID)
和 (SELECT CompanyName FROM Suppliers WHERE Suppliers.SupplierID = Products.SupplierID)
- 是 SELECT
查詢,會傳回每個產品的單一值做為外部 SELECT
陳述式的資料行清單中的額外資料行。
或者,可以使用 JOIN
傳回每個產品的供應商和類別名稱。 下列查詢會傳回與上述相同的輸出,但使用 JOIN
來取代子查詢:
SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued,
Categories.CategoryName,
Suppliers.CompanyName as SupplierName
FROM Products
LEFT JOIN Categories ON
Categories.CategoryID = Products.CategoryID
LEFT JOIN Suppliers ON
Suppliers.SupplierID = Products.SupplierID
JOIN
會根據一些準則,將某個資料表中的記錄與另一個資料表中的記錄合併。 例如,在上述查詢中,LEFT JOIN Categories ON Categories.CategoryID = Products.CategoryID
指示 SQL Server 將每個產品記錄與其 CategoryID
值符合產品 CategoryID
值之類別記錄相合併。 合併的結果可讓我們處理每個產品的相對應類別欄位 (例如 CategoryName
)。
注意
從關聯式資料庫查詢資料時,通常會使用 JOIN
。 如果您不熟悉 JOIN
語法,或需要稍微複習一下其使用方式,我建議 W3 Schools 的 SQL 連結教學課程。 同樣值得閱讀的是 SQL 線上叢書的JOIN
基本概念和子查詢基本概念章節。
由於 JOIN
和相互關聯的子查詢都可以用來擷取其他資料表的相關資料,因此許多開發人員多有疑惑並且想知道要使用哪種方法。 與我交談過的所有 SQL 大師,意見都大致相同,那就是這就效能方面來說並不重要,因為 SQL Server 會產生大致相同的執行計劃。 他們而後的建議是使用您和您的團隊最熟悉的技術。 值得指出的是,在給予此建議後,這些專家隨即表示,比起相互關聯子查詢,他們更偏好 JOIN
。
使用具類型的 DataSet 建置資料存取層時,使用子查詢搭配工具比較適當。 特別是,如果主查詢包含任何 JOIN
,則 TableAdapter 精靈將不會自動產生相對應的 INSERT
、UPDATE
和 DELETE
陳述式,但會在使用相互關聯的子查詢時自動產生這些陳述式。
若要探索此缺點,請在 ~/App_Code/DAL
資料夾中建立暫時具類型的 DataSet。 在 TableAdapter 組態精靈期間,選擇使用臨機操作 SQL 陳述式並輸入下列 SELECT
查詢 (見 [圖 1]):
SELECT ProductID, ProductName, Products.SupplierID, Products.CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued,
Categories.CategoryName,
Suppliers.CompanyName as SupplierName
FROM Products
LEFT JOIN Categories ON
Categories.CategoryID = Products.CategoryID
LEFT JOIN Suppliers ON
Suppliers.SupplierID = Products.SupplierID
圖 1:輸入包含 JOIN
的主查詢 (按一下以檢視完整大小的影像)
根據預設,TableAdapter 會根據主查詢自動建立 INSERT
、UPDATE
和 DELETE
陳述式。 如果您按一下 [進階] 按鈕,可以看到此功能已啟用。 儘管有此設定,但 TableAdapter 將無法建立 INSERT
、UPDATE
和 DELETE
陳述式,因為主查詢包含 JOIN
。
圖 2:輸入包含 JOIN
的主查詢
按一下 [完成] 以完成程序。 此時,您的 DataSet 設計工具將包含單一 TableAdapter,並且有一個 DataTable,其中對於 SELECT
查詢的資料行清單中傳回的每個欄位都包含資料行。 這包括 CategoryName
和 SupplierName
,如 [圖 3] 所示。
圖 3:DataTable 針對資料行清單中傳回的每個欄位包含一個資料行
雖然 DataTable 具有適當的資料行,但 TableAdapter 對於其 InsertCommand
、UpdateCommand
和 DeleteCommand
屬性都缺少值。 若要確認這一點,請按一下設計工具中的 TableAdapter,然後移至 [屬性] 視窗。 您會在該處看到 InsertCommand
、UpdateCommand
和 DeleteCommand
屬性設定為 [無] 。
圖 4:InsertCommand
、UpdateCommand
和 DeleteCommand
屬性設定為 (無) (按一下以檢視完整大小的影像)
為了解決這個缺點,我們可以透過 [屬性] 視窗手動提供 InsertCommand
、UpdateCommand
和 DeleteCommand
屬性的 SQL 陳述式和參數。 或者,我們也可以開始先將 TableAdapter 的主查詢設定為「不」包含任何 JOIN
。 這可為我們自動產生 INSERT
、UPDATE
和 DELETE
陳述式。 完成精靈之後,我們可以從 [屬性] 視窗手動更新 TableAdapter 的 SelectCommand
,使其包含 JOIN
語法。
雖然此方法可行,但使用臨機操作 SQL 查詢時相當脆弱,因為每當透過精靈重新設定 TableAdapter 的主查詢時,就會重新建立自動產生的 INSERT
、UPDATE
和 DELETE
陳述式。 這表示,如果我們以滑鼠右鍵按一下 TableAdapter,從操作功能表中選擇 [設定],然後再次完成精靈,我們稍後進行的所有自訂都將遺失。
所幸,TableAdapter 自動產生的 INSERT
、UPDATE
和 DELETE
陳述式的脆弱性,僅限於臨機操作 SQL 陳述式。 如果您的 TableAdapter 使用預存程序,您可以自訂 SelectCommand
、InsertCommand
、UpdateCommand
或 DeleteCommand
預存程序,然後重新執行 TableAdapter 組態精靈,而不需要擔心預存程序會遭到修改。
在接下來的幾個步驟中,我們將建立 TableAdapter,一開始會使用省略任何 JOIN
的主查詢,以便自動產生相對應的插入、更新和刪除預存程序。 接著,我們將更新 SelectCommand
,以便使用 JOIN
從相關資料表傳回其他資料行。 最後,我們將建立相對應的商務邏輯層類別,並示範如何在 ASP.NET 網頁中使用 TableAdapter。
步驟 1:使用簡化的主查詢建立 TableAdapter
對於本教學課程,我們將針對 NorthwindWithSprocs
DataSet 中的 Employees
資料新增 TableAdapter 和具強式型別的 DataTable。 Employees
資料表包含 ReportsTo
欄位,會指定員工經理的 EmployeeID
。 例如,員工 Anne Dodsworth 的 ReportTo
值為 5 ,也就是 Steven Buchanan 的 EmployeeID
。 因此,Anne 向她的經理 Steven 報告。 除了報告每個員工的 ReportsTo
值之外,我們也可能想要擷取其經理的姓名。 這可以使用 JOIN
來達成。 但是,在最初建立 TableAdapter 時使用 JOIN
,會防止精靈自動產生相對應的插入、更新和刪除功能。 因此,我們開始將先建立 TableAdapter,其主查詢不包含任何 JOIN
。 然後,在步驟 2 中,我們將更新主查詢預存程序,透過 JOIN
擷取經理的姓名。
從開啟 ~/App_Code/DAL
資料夾中的 NorthwindWithSprocs
DataSet 開始。 以滑鼠右鍵按一下設計工具,從操作功能表中選取 [新增] 選項,然後挑選 TableAdapter 功能表項。 這將啟動 [TableAdapter 組態] 精靈。 如 [圖 5] 所述,精靈會建立新的預存程序,然後按 [下一步]。 如需複習從 TableAdapter 精靈建立新預存程序的步驟,請參考針對具類型的 DataSet 的 TableAdapter 建立新的預存程序教學課程。
圖 5:選取 [建立新預存程序] 選項 (按一下以檢視完整大小的映像)
針對 TableAdapter 的主查詢使用下列 SELECT
陳述式:
SELECT EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country
FROM Employees
由於此查詢不包含任何 JOIN
,因此 TableAdapter 精靈會以相對應的 INSERT
、UPDATE
和 DELETE
陳述式自動建立預存程序,以及執行主查詢的預存程序。
下列步驟可讓我們命名 TableAdapter 的預存程序。 使用名稱 Employees_Select
、Employees_Insert
、Employees_Update
和 Employees_Delete
,如 [圖 6] 所示。
圖 6:命名 TableAdapter 的預存程序 (按一下以檢視完整大小的影像)
最後一個步驟會提示我們命名 TableAdapter 的方法。 使用 Fill
和 GetEmployees
做為方法名稱。 另外,務必將 [建立方法以直接將更新傳送至資料庫 (GenerateDBDirectMethods)] 保留為核取狀態。
圖 7:命名 TableAdapter 的方法 Fill
和 GetEmployees
(按一下以檢視完整大小的影像)
完成精靈之後,請花點時間檢查資料庫中的預存程序。 您應該會看到四個新的項目:Employees_Select
、Employees_Insert
、Employees_Update
和 Employees_Delete
。 接下來,檢查剛剛建立的 EmployeesDataTable
和 EmployeesTableAdapter
。 DataTable 對於主查詢所傳回的每個欄位都包含一個資料行。 按一下 TableAdapter,然後移至 [屬性] 視窗。 您會看到 InsertCommand
、UpdateCommand
和 DeleteCommand
屬性已正確設定為呼叫相對應的預存程序。
圖 8:TableAdapter 包含插入、更新和刪除功能 (按一下以檢視完整大小的影像)
在自動建立、更新和刪除預存程序並正確設定InsertCommand
UpdateCommand
和 DeleteCommand
屬性之後,我們就可以自訂SelectCommand
預存程序,以傳回關於每個員工的經理的其他資訊。 具體而言,我們需要更新Employees_Select
預存程序,以使用 JOIN
並傳回經理的 FirstName
和 LastName
值。 更新預存程序之後,我們需要更新 DataTable,使其包含這些額外的資料行。 我們將在步驟 2 和 3 中處理這兩項工作。
步驟 2:自訂預存程序以包含JOIN
開始先移至伺服器總,向下切入 Northwind 資料庫的 [預存程序] 資料夾,然後開啟 Employees_Select
預存程序。 如果您沒有看到此預存程序,請以滑鼠右鍵按一下 [預存程序] 資料夾,然後選擇 [重新整理]。 更新預存程序,讓它能夠使用 LEFT JOIN
傳回經理的名字和姓氏:
SELECT Employees.EmployeeID, Employees.LastName,
Employees.FirstName, Employees.Title,
Employees.HireDate, Employees.ReportsTo,
Employees.Country,
Manager.FirstName as ManagerFirstName,
Manager.LastName as ManagerLastName
FROM Employees
LEFT JOIN Employees AS Manager ON
Employees.ReportsTo = Manager.EmployeeID
更新 SELECT
陳述式之後,移至 [檔案] 功能表並選擇 [儲存 Employees_Select
] 來儲存變更。 或者,您也可以按下工具列中的 [儲存] 圖示,或按 Ctrl+S。 儲存變更之後,以滑鼠右鍵按一下伺服器總管中的 Employees_Select
預存程序,然後選擇 [執行]。 這會執行預存程序,並在 [輸出] 視窗中顯示其結果 (見 [圖 9])。
圖 9:預存程序結果顯示在輸出視窗中 (按一下以檢視完整大小的影像)
步驟 3:更新 DataTable s 資料行
此時,Employees_Select
預存程序傳回 ManagerFirstName
和 ManagerLastName
值,但 EmployeesDataTable
遺漏這些資料行。 這些遺漏的資料行可以透過下列兩種方式之一新增至 DataTable:
- 手動 - 以滑鼠右鍵按一下 DataSet 設計工具中的 DataTable,然後從 [新增] 選單中,選擇 [資料行]。 然後您可以命名該資料行,並相應地設定其屬性。
- 自動 - TableAdapter 組態精靈會更新 DataTable 資料行,以反映預存程序所傳回的
SelectCommand
欄位。 使用臨機操作 SQL 陳述式時,精靈也會移除InsertCommand
、UpdateCommand
和DeleteCommand
屬性,因為SelectCommand
現在包含JOIN
。 但是,使用預存程序時,這些命令屬性會保持不變。
我們已在先前教學課程中探討手動新增 DataTable 資料行,包括使用主記錄項目符號清單搭配詳細資料 DataList 的主要/詳細資料報告和上傳檔案,我們將在下一個教學課程中更詳細地探討此程序。 不過,對於本教學課程,讓我們透過 TableAdapter 組態精靈使用自動方法。
首先,以滑鼠右鍵按一下 EmployeesTableAdapter
,然後從操作功能表中選取 [設定]。 這會顯示 TableAdapter 組態精靈,其中列出用來選取、插入、更新和刪除的預存程序,以及其傳回值和參數 (如果有的話)。 [圖 10] 顯示此精靈。 我們在這裡可以看到 Employees_Select
預存程序現在傳回 ManagerFirstName
和 ManagerLastName
欄位。
圖 10:精靈顯示 Employees_Select
預存程序的更新資料行清單 (按一下以檢視完整大小的影像)
按一下 [完成] 以完成精靈。 返回 DataSet 設計工具時,EmployeesDataTable
會包含兩個額外的資料行: ManagerFirstName
和 ManagerLastName
。
圖 11:EmployeesDataTable
包含兩個新的資料行 (按一下以檢視完整大小的影像)
為了說明更新的 Employees_Select
預存程序已生效,而且 TableAdapter 的插入、更新和刪除功能仍然正常運作,讓我們建立一個網頁,讓使用者可以檢視和刪除員工。 不過,在我們建立這類頁面之前,必須先在商務邏輯層中建立新的類別,以便使用 NorthwindWithSprocs
DataSet 中的員工。 在步驟 4 中,我們將建立一個 EmployeesBLLWithSprocs
類別。 在步驟 5 中,我們將從 ASP.NET 頁面使用此類別。
步驟 4:實作商務邏輯層
在 ~/App_Code/BLL
資料夾中建立一個新類別檔案,名為 EmployeesBLLWithSprocs.cs
。 這個類別會模擬現有 EmployeesBLL
類別的語意,不過這個新類別會提供較少的方法,並使用 NorthwindWithSprocs
DataSet (而不是 Northwind
DataSet)。 將下列程式碼加入 EmployeesBLLWithSprocs
類別。
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 EmployeesBLLWithSprocs
{
private EmployeesTableAdapter _employeesAdapter = null;
protected EmployeesTableAdapter Adapter
{
get
{
if (_employeesAdapter == null)
_employeesAdapter = new EmployeesTableAdapter();
return _employeesAdapter;
}
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Select, true)]
public NorthwindWithSprocs.EmployeesDataTable GetEmployees()
{
return Adapter.GetEmployees();
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteEmployee(int employeeID)
{
int rowsAffected = Adapter.Delete(employeeID);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
}
EmployeesBLLWithSprocs
類別的 Adapter
屬性會傳回 NorthwindWithSprocs
DataSet 的 EmployeesTableAdapter
的執行個體。 這會由類別的 GetEmployees
和 DeleteEmployee
方法使用。 GetEmployees
方法會呼叫 EmployeesTableAdapter
相對應的 GetEmployees
方法,這個方法會叫用 Employees_Select
預存程序,並在 EmployeeDataTable
中填入其結果。 DeleteEmployee
方法同樣會呼叫 EmployeesTableAdapter
的 Delete
方法,這個方法會叫用 Employees_Delete
預存程序。
步驟 5:使用展示層中的資料
完成 EmployeesBLLWithSprocs
類別之後,我們就可以透過 ASP.NET 頁面處理員工資料。 開啟 AdvancedDAL
資料夾中的 JOINs.aspx
頁面,並將 GridView 從 [工具箱] 拖曳至設計工具,將其 ID
屬性設定為 Employees
。 接下來,從 GridView 的智慧標記,將方格繫結至名為 EmployeesDataSource
的新 ObjectDataSource 控制項。
將 ObjectDataSource 設定為使用 EmployeesBLLWithSprocs
類別,然後從 [SELECT] 和 [DELETE] 索引標籤,確定已從下拉式清單中選取 GetEmployees
和 DeleteEmployee
方法。 按一下 [完成] 以完成 ObjectDataSource 的組態。
圖 12:將 ObjectDataSource 設定為使用 EmployeesBLLWithSprocs
類別 (按一下以檢視完整大小的影像)
圖 13:讓 ObjectDataSource 使用 GetEmployees
和 DeleteEmployee
方法 (按一下以檢視完整大小的影像)
Visual Studio 會針對每個 EmployeesDataTable
資料行將 BoundField 新增至GridView。 移除 Title
、LastName
、FirstName
、ManagerFirstName
和 ManagerLastName
以外的所有 BoundFields,並將最後四個 BoundFields 的 HeaderText
屬性分別重新命名為姓氏、名字、經理名字和經理姓氏。
若要允許使用者從此頁面刪除員工,我們需要執行兩件事。 首先,藉由從其智慧標記中核取 [啟用刪除] 選項,指示 GridView 提供刪除功能。 其次,將 ObjectDataSource 的 OldValuesParameterFormatString
屬性從 ObjectDataSource 精靈設定的值 (original_{0}
) 變更為其預設值 ({0}
)。 進行這些變更之後,您的 GridView 和 ObjectDataSource 宣告式標記看起來應該如下所示:
<asp:GridView ID="Employees" runat="server" AutoGenerateColumns="False"
DataKeyNames="EmployeeID" DataSourceID="EmployeesDataSource">
<Columns>
<asp:CommandField ShowDeleteButton="True" />
<asp:BoundField DataField="Title"
HeaderText="Title"
SortExpression="Title" />
<asp:BoundField DataField="LastName"
HeaderText="Last Name"
SortExpression="LastName" />
<asp:BoundField DataField="FirstName"
HeaderText="First Name"
SortExpression="FirstName" />
<asp:BoundField DataField="ManagerFirstName"
HeaderText="Manager's First Name"
SortExpression="ManagerFirstName" />
<asp:BoundField DataField="ManagerLastName"
HeaderText="Manager's Last Name"
SortExpression="ManagerLastName" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="EmployeesDataSource" runat="server"
DeleteMethod="DeleteEmployee" OldValuesParameterFormatString="{0}"
SelectMethod="GetEmployees" TypeName="EmployeesBLLWithSprocs">
<DeleteParameters>
<asp:Parameter Name="employeeID" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
透過瀏覽器瀏覽頁面來測試該頁面。 如 [圖 14] 所示,頁面會列出每位員工及其經理的姓名 (假設他們有一個)。
圖 14:Employees_Select
預存程式序中的 JOIN
傳回經理的姓名 (按一下以檢視完整大小的影像)
按一下 [刪除] 按鈕會啟動刪除工作流程,這會在 Employees_Delete
預存程序執行時達到最高點。 然而,預存程序中嘗試的 DELETE
陳述式因為外部鍵條件約束而失敗 (見 [圖 15])。 具體來說,每個員工在 Orders
資料表中都有一或多筆記錄,導致刪除失敗。
圖 15:刪除具有相對應訂單的員工會導致外部鍵條件約束 (按一下以檢視完整大小的影像)
若要允許刪除員工,您可以:
- 更新外部鍵條件約束以串聯刪除,
- 從
Orders
資料表手動刪除您想要刪除之員工的記錄,或 - 更新
Employees_Delete
預存程序,先刪除Orders
資料表中的相關記錄,再刪除Employees
記錄。 我們在針對具類型的 DataSet 的 TableAdapter 使用現有預存程序教學課程中已經討論過這個方法。
我把這留作讀者練習之用。
摘要
使用關聯式資料庫時,查詢通常會從多個相關資料表提取其資料。 相互關聯的子查詢和 JOIN
提供兩種不同的方法,用於查詢中從相關資料表存取資料。 在先前的教學課程中,我們最常使用相互關聯的子查詢,因為 TableAdapter 無法針對涉及 JOIN
的查詢自動產生 INSERT
、UPDATE
和 DELETE
陳述式。 雖然可以手動提供這些值,但當使用臨機操作 SQL 陳述式時,在 TableAdapter 組態精靈完成時將會覆寫任何自訂。
所幸,使用預存程序建立的 TableAdapter 不如使用臨機操作 SQL 陳述式所建立般的脆弱。 因此,建立 TableAdapter 是可行的,在使用預存程序時其主查詢會使用 JOIN
。 在本教學課程中,我們已了解如何建立這類 TableAdapter。 我們開始先針對該 TableAdapter 主查詢使用沒有 JOIN
的 SELECT
查詢,以便自動建立相對應的插入、更新和刪除預存程序。 當 TableAdapter 完成初始組態時,我們擴增了 SelectCommand
預存程序,以使用 JOIN
並重新執行 TableAdapter 組態精靈來更新 EmployeesDataTable
的資料行。
重新執行 TableAdapter 組態精靈會自動更新 EmployeesDataTable
資料行,以反映 Employees_Select
預存程序傳回的資料欄位。 或者,我們可以手動將這些資料行新增至 DataTable。 我們將在下一個教學課程中探索手動新增資料行至 DataTable。
祝您程式設計愉快!
關於作者
Scott Mitchell,七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 創始人,自 1998 年以來便開始使用 Microsoft Web 技術。 Scott 擔任獨立顧問、講師和作家。 他的新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以透過 mitchell@4GuysFromRolla.com 或他的部落格 (可以在 http://ScottOnWriting.NET 找到) 與他聯繫。
特別感謝
本教學課程系列已經過許多熱心的檢閱者檢閱。 本教學課程的主要檢閱者是 Hilton Geisenow、David Suru 和 Teresa Murphy。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果有,請發信到 mitchell@4GuysFromRolla.com 。