source: http://www.cnblogs.com/JimmyZhang/archive/2007/10/03/913337.html
PDF 瀏覽:http://www.tracefact.net/document/Object-Relational-Mapping-Part1.pdf
對象關係映射 - Part.1引言
大多數情況下,大家都在使用物件導向的思想進行程式開發與設計。與此同時,和我們打交道最多的資料庫莫過於關聯式資料庫了。而如何使這兩個不同領域的對象相互協作自然成了我們日常討論最多的話題之一。
作為本系列的第一篇文章,我主要向大家介紹了理解對象關係映射的一些預備知識和基礎概念。主要包括:一對一關聯性、物件導向基礎、關係基礎並對 對象與關係之間存在的差異作了簡要的討論。
對象關係映射的英文全名為:Object Relational Mapping,簡寫為:ORM。為了簡便,本文下面提到對象關係映射,均使用 ORM 。
一對一關聯性
在講述對象關係映射 ORM 之前,我們首先需要瞭解一下什麼是一對一關聯性以及 如何?一對一關聯性。一對一關聯性也叫做超類/子類別關係。
在實際的開發中,大家最熟悉的資料庫關係可能就是一對多了。比如說一個分類下有很多篇隨筆,又比如一篇隨筆下有很多條評論。不知不覺中,對於需要使用一對一來實現的情況,也不假思索地使用一對多的方式去實現了。
我們來看這樣一個例子,為了示範我做了簡化:
假如我們正在設計一個論壇的使用者表User,那麼一般來說需要這樣幾個欄位:UserId,使用者ID;Name,使用者名稱稱;Password,密碼;Email,電子郵件。我們還要設定管理員和版主,所以我們繼續添加欄位:AdminPwd,後台密碼;Level,使用者層級:0,普通會員;1,版主;2,管理員。
這個表的建立指令碼是這樣的:
Create Table [User]
(
UserId Int Identity(1,1),
Name Varchar(30) Not Null,
Password Varchar(50) Not Null,
Email Varchar(50) Not Null,
AdminPwd Varchar(50) Null,
Level TinyInt Not Null Default 0 Constraint ck_UserLevel Check(Level between 0 and 2) --0,普通會員;1,版主;2,管理員
Constraint pk_User Primary Key(UserId) -- 建立主鍵
)
很快就會發現,使用者與管理員的比例往往是 1000:1。這時候對於99.9%的使用者來說,它的 AdminPwd 欄位都是Null,Level欄位是 0。於是,為了避免這種不必要的空間浪費,我們將管理員抽象出來,建立一個Admin表。
這時候,問題出來了,很多人都是採用這種方式建立的:
Create Table [User]
(
UserId Int Identity(1,1),
Name Varchar(30) Not Null,
Password Varchar(50) Not Null,
Email Varchar(50) Not Null,
Constraint pk_User Primary Key(UserId) -- 建立主鍵
)
Create Table Admin
(
AdminId Int Identity(1,1),
AdminPwd Varchar(50) Null,
Level TinyInt Not Null Default 1 Constraint ck_UserLevel Check(Level between 1 and 2),
FKUserId Int Not Null
Constraint pk_Admin Primary Key(AdminId) -- 建立主鍵
)
在Admin表中,不少朋友想當然地用 FKUserId 去對應 User 表中的 UserId,以此來實現 管理員與使用者的對應關係。實際上,這是典型的一對多關聯性的做法。因為,你完全可以讓兩個不同的管理員擁有著相同的 FKUserId 欄位值,也就是對應User表中的同一個使用者。這時候,已經破壞了資料一致性。而且,還產生了一個沒有任何意義的欄位:AdminId。
那麼,合理的方案是什麼呢?那就是採用一對一關聯性,具體的實現指令碼如下:
Create Table [User]
(
UserId Int Identity(1,1), -- 主表的主鍵
Name Varchar(30) Not Null,
Password Varchar(50) Not Null,
Email Varchar(50) Not Null,
Constraint pk_User Primary Key(UserId)
)
Create Table Admin
(
UserId Int, -- 不能是自增型
AdminPwd Varchar(50) Null,
Level TinyInt Not Null Default 1 Constraint ck_UserLevel Check(Level between 1 and 2), -- 1,版主;2,管理員
Constraint pk_Admin Primary Key(UserId) -- 建立從表主鍵
)
Alter Table Admin -- 建立從表外鍵
Add Constraint fk_Admin_User Foreign Key(UserId) References User (UserId)
On Delete No Action On Update No Action
我們看看現在與之前有什麼區別?
- AdminId 欄位重新命名為了 UserId,為什麼這麼做?我的原則是:如果一個表中的欄位與另一個表中的欄位含義相同,那麼它們的名字也相同,外鍵包含的欄位視情況而定。
- UserId 欄位不再是自增型了,這個很容易理解:它要與User表中的UserId對應,以表示是同一個人。User表中的UserId已經是自增型了,它當然不能再自增了,不然如何對應?
- 取消了 FKUserId 欄位,原因很簡單,因為 UserId 現在不光是主鍵,也是外鍵,用來對應 User 表的 UserId 欄位。
在這個執行個體中:
- UserId是主鍵,保證了在Admin表中它只能有一個,不能重複。
- UserId是外鍵,保證了它一定對應一條已經存在於User表中的記錄,也就是一個使用者。
我們現在總結一下,一對一關聯性實施的特點:
- 主表的主鍵為自增型或者其他任何能唯一標識記錄的類型。
- 從表的主鍵(包含的欄位) 與主表的主鍵(包含的欄位) 名稱與含義相同。
- 從表的外鍵(包含的欄位) 與從表的主鍵(包含的欄位) 相同。
- 從表的外鍵(包含的欄位) 對應主表的主鍵(包含的欄位)。
物件導向基礎類型
個人認為,類型(Type)是物件導向思想中最重要的一個概念,許多其他的概念都是由它推演出來的。而很多朋友以為定義了一個類(Class)就是定義了一個類型,這樣的理解有所偏差。
類型是由類的介面決定的,當我們提到某個對象是什麼類型的時候,實際上說的是它有什麼樣的介面,而並不關心其介面是如何?的。反過來思考,就是:類型是 詳細定義了的、對象所要支援的介面。
NOTE:如果一個對象實現了 類型 定義的 全部介面,那麼我們就說: 這個對象實現了這個 類型,說得順口一點,就是:某某類型的對象。如果一個對象實現了很多個類型的介面,那麼它就同時是很多個類型。
GOF四人組在其著作《設計模式》第13頁中這樣提到:A type is a name used to denote a particular interface. We speak of an object as having the type "Window" if it accepts all requests for the operation defined in the interface "Window".
翻譯過來是:類型是標識一種特定介面的名稱。如果有一個對象,它滿足定義在"Window"介面中的全部要求,那麼我們就說這個對象具有"Window"類型。
聯絡
類型並不是孤立存在的,一個類型可以與其他類型存在聯絡。如果兩個類型之間存在著聯絡,那麼某個類型的對象 就可以 轉換為另一個類型的對象。
NOTE:大家整天在說物件導向的多態性,奇怪為什麼一個類的執行個體可以轉換成它的介面類型,而介面什麼都沒有做,只不過定義了一堆方法和屬性簽名而已。
現在明白了吧?這是因為類型間的聯絡,介面定義了類型,而類(class)與之聯絡。
類
類定義了對象(反過來思考,就是類的執行個體)的具體實現。它定義了對象擁有什麼樣的類型,說得通俗一點,就是類實現了介面(抽象類別是個特例)。
繼承
繼承可以是類型繼承,也可以是類繼承。
當是類型繼承的時候:如果一個對象是類型B,而類型B繼承自類型A,那麼我們可以說:此對象也是類型A。
當是類繼承的時候:我們說一個類繼承了另一個類的實現,同時,繼承類並不喪失對其基類的覆蓋能力。
狀態
狀態這一概念相對來說比較好理解,它通常指的是對象擁有的值的集合。
行為
對象的行為是指:
- 個對象所提供的操作的集合,也就是它的介面。
- 對象對於調用它的其他對象所做出的回應,通常來說就是方法的傳回值。
- 操作對於對象本身所造成的影響。
對於一個對象的所有互動和瞭解都是通過它的行為來完成。
封裝
封裝使得對象成為一個不依賴於其他對象的獨立個體。外界對其內部的實現是不可見的。舉個例子,當你與一個封裝好了的對象進行互動,你只能通過它所提供的介面,而對於介面以內的具體實現是不能也不應該知道的。
NOTE:本章僅為後面章節的說明提供預備知識,內容比較淺顯,如果您對物件導向程式設計感興趣,可以參考相關書籍。
關係基礎
本章講述的是關係(Relation),儘管有的概念與之前物件導向的概念有幾分相似,但請不要試圖用物件導向的思想去理解關係中的概念。
關係
關係定義了一組屬性,這組屬性群組合起來構成一個有意義的事物,以我們之前討論過的一對一關聯性為例,它之中的一個關係就是:User{Name, Email, Gender, BirthDay}。
屬性
屬性是關係的組成部分,它提供了區分不同關係的依據。舉個例子,為什麼使用者關係User{Name, Email, Gender, BirthDay} 和 汽車關係 Car{Name, Color, Speed, Price}不同? 不是因為“User”的意思翻譯過來是“使用者”,“Car”翻譯過來是“汽車”,而是因為它們的屬性不同。
屬性還定義了屬性值的範圍。例如,User關係的屬性Gender (性別),它的取值範圍就只可能是{男, 女},不可能再有其它值。
範圍
簡單地理解,範圍就是一種資料類型,或者說,是值的一個取值範圍。例如上面提到的Gender屬性,它的範圍就是{男, 女}。
元組
就如用說“汽車”一樣,關係是在很高層次上的一個抽象,並不代表具體的事物。只有當關係的所有屬性都有了其範圍內的特定值時,它才能代表一個實際的事物。
當我們給一個關係的屬性都賦予了其範圍內的某個特定值以後,就構成了一個元組。
我們再以上面的User關係為例,它的一個元組是:
<User Name="張子陽" Gender="男" Email="jimmy_dev@163.com" BirthDay="1982-12-8">
對於多個元組來說,如果它們的所有屬性值都相同,那麼我們就說它們是同一個元組。
關係值
一組元組就構成了關係值,我們再以User為例,它的一個關係值是:
{
<User Name="張子陽" Gender="男" Email="jimmy_dev@163.com" BirthDay="1982-12-8">
<User Name="王五" Gender="男" Email="Wang5@cnblogs.com" BirthDay="1983-7-17">
<User Name="李四" Gender="女" Email="Li4@cnblogs.com" BirthDay="1984-5-20">
}
對於關係值來說:
關係和表
在關聯式資料庫中,關係通常表現為表的形式,例如上面的User關係,通常在資料庫中表述成一張類似下面的User表。
User 表
| Name |
Gender |
Email |
BirthDay |
| 張子陽 |
男 |
jimmy_dev@163.com |
1982-12-8 |
| 李四 |
女 |
li4@cnblogs.com |
1983-7-17 |
| 王五 |
男 |
wang5@cnblogs.com |
1984-5-20 |
關係系統和資料庫系統的術語對應
| 資料庫系統 |
關係系統 |
| 表(Table) |
關係(Relation) |
| 行(Row) |
元組(Tuple) |
| 列(Column) |
屬性(Attribute) |
| 資料類型(Data Type) |
範圍(Domain) |
| 列值(Column Value) |
屬性值(Attribute Value) |
基關係
現在請回想一下本文第一章所講述的一對一關聯性,在前面的範例中,相對於Admin關係來說,User關係就是一個基關係。
繼承關係
子關係是相對於基關係而言的,相對於User來說,Admin關係就是一個繼承關係。
NOTE:本章僅為後面章節的說明提供預備知識,內容比較淺顯,如果您對關係系統感興趣,可以參考《離散數學》和《關聯式資料庫系統概論》。
對象 vs 關係
如果我們現在要對“汽車”這一概念進行建模,它有四個屬性:Name(車名)、Head(車頭)、Body(車身)、Rear(車尾)。
物件導向的做法
我們首先定義一個 Car 類:
public class Car{
public string Name;
public string Head;
public string Body;
public string Rear;
public Car(name, head, body, rear){
this.Name = name;
this.Head = head;
this.Body = body;
this.Rear = rear;
}
}
當我們需要一輛寶馬(BMW)、一輛平治(MB)、一輛奧迪(Audi)的時候:
Car BMW = new Car("寶馬", "寶馬車頭", "寶馬車身", "寶馬車尾");
Car MB = new Car("平治", "平治車頭", "平治車身", "平治車尾")
Car Audi = new Car("奧迪", "奧迪車頭", "奧迪車身", "奧迪車尾")
NOTE:上面這個例子實際並不合適,你可以將CarHead、CarBody想象成還擁有各自屬性的實體類。
關聯式資料庫的做法
建四張表,分別為:CarName、CarHead、CarBody、CarRear,然後將三輛車分成四個部分,將每個部分分別放置到合適的表中,需要用的時候,再進行組裝。
問題提出
如果僅僅只是如此而已,問題似乎很容易解決:讓類的屬性去對應資料庫中表的欄位。
現在,請回想之前物件導向基礎那一節的內容,對象不僅僅只有屬性而已,還包括 行為、封裝、繼承、聯絡 等複雜特性。它們並不能夠在資料庫中找到良好的對應,這樣的問題該如何解決呢?
限於篇幅,本文就介紹的這裡,更多的內容將在後續文章中講述。
總結
在本文中,我們首先瞭解了什麼是一對一關聯性 以及 如何?一對一關聯性。
隨後我們對 物件導向 和 關係系統 的基礎概念和理論做了複習和回顧。
最後,我們結合之前介紹的知識,提出了在軟體開發中物件導向與關係系統之間存在的差異。
希望本文能給你帶來協助。