上篇博客《我們該如何設計數據庫(三)》寫出來之后,深感自己寫得不夠清晰,虎頭蛇尾,描述問題用了很多篇幅,而問題的解決方案及其優缺點卻是一筆帶過,於是就寫下了這篇博客來負荊請罪
示例代碼下載:點擊這里下載
示例代碼說明見下文
首先讓我們來回顧一下《我們該如何設計數據庫(三)》中描述的問題:
現在有一個系統,我們暫時假設為學校選課系統。
系統要按學校來賣。每個學校的選課邏輯都是一樣的,而表中的數據有共性,但是也有差異性。比如說基本的Teacher表結構是這樣的:
現在把系統賣給A學校。A學校除了的Teacher表除了用戶名和密碼之外,還要儲存老師的FirstName和LastName,那么表結構變化如下:
現在B學校也買了我們的系統。他們的Teacher表不要FirstName和LastName,但是要儲存教師的工號“Number”,表結構如下:
好,現在我們的問題出來了:怎么去解決這種差異性
大致有3種解決方法
1、表中加冗余
2、增加冗余表
3、model繼承
第一種思路:表中加冗余,在上篇已經分析過了,在此就稍微說一下優缺點。
優點在於簡單:思路簡單,實現也簡單
缺點在於維護困難:①如果我們的系統賣了很多所學校,那么對於系統維護人員來說,這是一場噩夢 ②違背開閉:每次加字段都要去修改已有Model
第二種思路:增加冗余表
如上一篇文章中徐少俠所言,我們也可以這樣來設計,用一張冗余表來儲存差異字段,如圖所示:
FirstName、LastName、Number對應擴展表內的三行
這樣的好處在於:思路簡單,實現相比第一種思路復雜一些,但是也不算復雜
缺點在於:①Join
②違背開閉:每次加字段都要去修改已有Model
(徐少俠在留言中也說了缺點:查詢數據時候比較辛苦.只能用在讀寫壓力不高的地方.)
第三種思路:Model繼承
這種方法,比較適合使用ORM,或者說適合有Code First的ORM
在這里使用的是EF5(EF5推薦環境是.NET 4.5,不過.NET 4也無傷大雅)
public class Identifier { [Key] public int ID { get; set; } } public interface Contact { string Phone { get; set; } string Email { get; set; } } public class TeacherBase : Identifier, Contact { [StringLength(50)] public string UserName { get; set; } [StringLength(50)] public string Pwd { get; set; } [StringLength(50)] public string Phone { get; set; } [StringLength(50)] public string Email { get; set; } }
這樣就是我們的Teacher基礎表。
那么我們的系統賣給我A學校,A學校Teacher表有兩個自己的差異字段:FirstName,LastName。那么Model就要這樣寫
namespace Model.A { public class Teacher : TeacherBase { [StringLength(50)] public string FirstName { get; set; } [StringLength(50)] public string LastName { get; set; } } }
生成出來的數據庫如圖:
然后又把系統賣給了B學校。B學校系統的部署是獨立的。B學校Teacher表差異字段為Number:
namespace Model.B { public class Teacher : TeacherBase { [StringLength(50)] public string Number { get; set; } } }
生成出來的數據庫如圖:
這樣做的優勢在於:
1、使用了OO的思想來設計Model,更易於理解與后期維護。
對於查看類的繼承關系,VS也提供了很好的支持,比如說你可以這樣看:
你也可以這樣看:
2、更清晰的關注點分離.
其實這個是Code First的好處,讓程序員可以直接從Model寫起,不必關注數據庫中的表結構
如果您對於使用ORM自生成數據庫有疑問,請自行百度"Code First"和"數據遷徙"。也歡迎留言討論
這樣做的缺點在於:
1、依賴ORM
2、違背開閉原則:要修改Namespace來切換Model(詳情見下文) 想要切換Model,要修改一處地方(見下文),雖然修改很少,但還是讓人不爽
3、違背了第三范式:
如Contact,根據第三范式應該獨立作為一個表,但是這里卻是寫入了Teacher表中
也的確有無數人——朋友,同事,網友——吐槽過我對於第三范式的違反,但是我還是堅持:嚴格的一對一關系,就是應該寫到各個表中,而不是獨立作為一個表
違反第三范式的好處在於:更少的Join
而壞處在於:如果要修改,則要修改很多地方。例如Teacher和Student都有Contact,那么如果Contact要加一列Fax,那么要修改兩處地方,這可能帶來額外的錯誤。但是這個缺點因為使用了Model繼承,將不再那么明顯:修改Contact接口,然后修改實現了Contact接口的Teacher與Student的Model;因為有智能提示的存在,這樣不會出錯
為了方便理解,我上傳了源代碼:點擊這里下載
請用VS2010打開,VS2008我不確定能不能運行。請確保有本地數據庫的權限
打開之后直接運行,會在本地數據庫生成DBaccess.B.Context名字的數據庫
在程序中我沒有寫數據遷徙,所以如果您修改了Model,要把之前生成的數據庫刪掉之后再運行;直接運行會報錯
如果想切換Model,將“Test”中的using Model.B改為using Model.A即可
這也意味着切換數據庫要修改命名空間。對於這個問題我還沒有好的解決辦法。本來是想用反射工廠來解決這個問題(代碼中未實現的ModelFactory),但是反射出來的是Object,要As了之后才能用
如果有哪位大牛有解決辦法,求告知,小弟在此跪謝
現在實現了ModelFactory,使用了預編譯指令來選擇NameSpace:
#define A #if B using Model.B; using DBaccess.B; #endif #if A using Model.A; using DBaccess.A; #endif
若要切換Model,將#define B 改為 #define A 即可
就此擱筆
PS:12306是一群小學生作的么?
再PS:有園友批評我“手中有個錘子,看到什么都是釘子”,說的是我什么問題都想用ORM來解決。關於錘子問題我不否認,但是那個錘子是NOSQL,只是現在還沒寫到而已