前言
程式設計中免不了要跟資料打交道, 在進行資料互動的過程中,我們可以采取很多.net自帶的對象來幫助我們承載資料,如,datatable,dataset或者dataview,并把它們作為資料資料顯示控件的資料原,通過資料邦定來顯示資料..斷開連接配接的資料使我們可以利用新的緩存技術,進而大大提高了應用程式的性能。這些類的功能使我們能夠編寫出更智能、更強大的函數,同時還能減少(有時候甚至是大大減少)常見活動所需的代碼數量。
有些情況下非常适合使用 DataSet,例如在設計原型、開發小型系統和支援實用程式時。但是,在企業系統中使用 DataSet 可能并不是最佳的解決方案,因為對企業系統來說,易于維護要比投入市場的時間更重要。今天我們的目的就是探讨一種适合處理此類工作的 DataSet 的替代解決方案,即:自定義實體與集合。我們的首要任務是了解 DataSet 的缺點,以便了解我們要解決的問題。
DataSet 存在的問題
缺少抽象
尋找替代解決方案的第一個也是最明顯的原因就是 DataSet 無法從資料庫結構中提取代碼。DataAdapter 可以很好地使您的代碼獨立于基礎資料庫供應商(Microsoft、Oracle、IBM 等),但不能抽象出資料庫的核心元件:表、列和關系。這些核心資料庫元件也是 DataSet 的核心元件。DataSet 和資料庫不僅共享通用元件,不幸的是,它們還共享架構。假定有下面這樣一個 Select 語句:
SELECT UserId, FirstName, LastName
FROM Users
我們知道這些值可以從 DataSet 中的 UserId、FirstName 和 LastName 這些 DataColumn 中獲得。在aspx頁面中借助資料邦定控件将資料顯示給客戶,然後不幸的是,如果果由于某種原因(為了性能而降級、為清楚起見而進行了标準化、要求發生了變化)導緻資料庫架構發生變化,變化就會一直影響 ASPX,即影響使用“FirstName”列名的 Databinder.Eval 行。這将立刻在您腦海中産生一個危險信号:資料庫架構的變化會一直影響到 ASPX 代碼嗎?聽起來不太像 N 層,對嗎?
DataSet 無法提供适當抽象的另一個原因是它要求開發人員必須了解基礎架構。我們所說的不是基礎知識,而是關于列名稱、類型和關系的所有知識。去掉這個要求不僅使您的代碼不像我們看到的那樣容易中斷,還使代碼更易于編寫和維護。簡單地說:
Convert.ToInt32(ds.Tables[0].Rows[i]["userId"]);
不僅難于閱讀,而且需要非常熟悉列名稱及其類型。理想情況下,您的業務層不需要知道有關基礎資料庫、資料庫架構或 SQL 的任何内容。如果您像上述代碼字元串中那樣使用 DataSet(使用 CodeBehind 并不會有任何改善),您的業務層可能會很薄。
弱類型
DataSet 屬于弱類型,是以容易出錯,這意味着無論何時從 DataSet 中檢索值,值都以 System.Object 的形式傳回,您需要對這種值進行轉換。您面臨轉換可能會失敗的風險。不幸的是,失敗不是在編譯時發生,而是在運作時發生。另外,在處理弱類型的對象時,Microsoft Visual Studio.NET (VS.NET) 等工具對您的開發人員并沒有太大的幫助。前面我們說過需要深入了解構架的知識,就是指這個意思。我們再來看一個非常常見的示例:
int userId = Convert.ToInt32(ds.Tables[0].Rows[0]("UserId"));
這段代碼顯示了從 DataSet 中檢索值的可能方法——可能您的代碼中到處都需要檢索值
不幸的是,這些代碼中的每一行都可能會産生大量的運作時錯誤:
1. | 轉換可能由于以下原因而失敗:
| ||||||
2. | ds.Tables(0) 可能傳回一個空引用(如果 DAL 方法或存儲過程中有任何部分失敗)。 | ||||||
3. | “UserId”可能由于以下原因而是一個無效的列名稱:
|
我們可以修改代碼并以更安全的方式編寫,即為 為轉換添加 try/catch,但這些對開發人員都沒有幫助。
“DataSet 是一個對象,對嗎?但它并不是域對象,它不是一個‘蘋果’或‘桔子’,而是一個‘DataSet’類型的對象。DataSet 是一隻碗(它知道支援資料存儲)。DataSet 是一個知道如何儲存行和列的對象,它非常了解資料庫。但是,我不希望傳回碗,我希望傳回域對象,例如‘蘋果’。”1
DataSet 使資料之間保持一種關系,使它們更強大并且能夠在關系資料庫中友善地使用。不幸的是,這意味着您将失去 OO 的所有優點。 因為 DataSet 不能作為域對象,是以無法向它們添加功能。通常情況下,對象具有字段、屬性和方法,它們的行為針對的是類的執行個體。
自定義實體類
與 DataSet 有關的大多數問題都可以利用 OO 程式設計的豐富功能在定義明确的業務層中解決。實際上,我們希望獲得按照關系組織的資料(資料庫),并将資料作為對象(代碼)使用。這個概念就是,不是獲得儲存汽車資訊的 DataTable,而是獲得汽車對象(稱為自定義實體或域對象)。
自定義實體是代表業務域的對象,是以,它們是業務層的基礎。如果您有一個使用者身份驗證元件,您就可能具有 User 和 Role 對象。電子商務系統可能具有 Supplier 和 Merchandise 對象,而房地産公司則可能具有 House、Room 和 Address 對象。在您的代碼中,自定義實體隻是一些類(實體和“類”之間具有非常密切的關系,就像在 OO 程式設計中使用的那樣)。一個典型的 User 類可能如下所示:
public class User {
private int userId;
private string userName;
private string password;
public int UserId {
get { return userId; }
set { userId = value; }
}
public string UserName {
get { return userName; }
set { userName = value; }
}
public string Password {
get { return password; }
set { password = value; }
}
public User() {}
public User(int id, string name, string password) {
this.UserId = id;
this.UserName = name;
this.Password = password;
}
為什麼能夠從它們獲益?
使用自定義實體獲得的主要好處來自這樣一個簡單的事實,即它們是完全受您控制的對象。具體而言,它們允許您:
• | 利用繼承和封裝等 OO 技術。 |
• | 添加自定義行為。 |
例如,我們的 User 類可以通過為其添加 UpdatePassword 函數而受益(我們可能會使用外部/實用程式函數對資料集執行此類操作,但會影響可讀性/維護性)。另外,它們屬于強類型,這表示我們可以獲得 IntelliSense 支援:

圖 1:User 類的 IntelliSense
最後,因為自定義實體為強類型,是以不太需要進行容易出錯的強制轉換:
int userId = user.UserId
int userId = Convert.ToInt32(ds.Tables("users").Rows(0)("UserId"))
對象關系映射
正如前文所讨論的那樣,此方法的主要挑戰之一就是處理關系資料和對象之間的差異。因為我們的資料始終存儲
在關系資料庫中,是以我們隻能在這兩個世界之間架起一座橋梁。對于上文的 User 示例,我們可能希望在數
據庫中建立一個如下所示的使用者表:
多層結構中自定義實體的使用
圖 2:User 的資料視圖
從這個關系架構映射到自定義實體是一個非常簡單的事情:
public User GetUser(int userId) {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command = new SqlCommand("GetUserById", connection);
command.Parameters.Add("@UserId", SqlDbType.Int).Value = userId;
SqlDataReader dr = null;
try{
connection.Open();
dr = command.ExecuteReader(CommandBehavior.SingleRow);
if (dr.Read()){
User user = new User();
user.UserId = Convert.ToInt32(dr["UserId"]);
user.UserName = Convert.ToString(dr["UserName"]);
user.Password = Convert.ToString(dr["Password"]);
return user;
}
return null;
}finally{
if (dr != null && !dr.IsClosed){
dr.Close();
}
connection.Dispose();
command.Dispose();
}
}
我們仍然按照通常的方式設定連接配接和指令對象,但接着建立了 User 類的一個新執行個體并從 DataReader 中填
充該執行個體。您仍然可以在此函數中使用 DataSet 并将其映射到您的自定義實體,但 DataSet 相對于
DataReader 的主要好處是前者提供了資料的斷開連接配接的視圖。在本例中,User 執行個體提供了斷開連接配接的視圖,
使我們可以利用 DataReader 的速度。
您現在了解什麼是自定義實體和自定義實體的好處了嗎,接下來我們将進一步探讨自定義集合的使用,以及
複雜系統中自定義實體的使用!