構造我自己的ORM
通過前面兩章的描述,我相信很多朋友都已經明白 我了下面將要討論到的ORM的實現方法了,那就是根據自定義Attribute來定義 O/R Mapping規則,然後通過反射來動態獲取此規則,動態構造SQL語句。
由於這個小東西(ORM)出生在深圳,所以我想來想去,她應該有個深圳 的名字,所以我就叫她“MiniORM”。不知道各位認為如何?
MiniORM采用的是ONE_INHERIT_TREE_ONE_CLASS(一個繼承樹對應於一個 表)的結構,雖然這種結構容易導致數據冗余,但是這種結構很簡單。另,本 MiniORM 僅僅考慮一個表一個PK,一個FK的情況。
MiniORM結構如下,為 了更便於理解和使用,我使用了3個類:
1、OrmWriter:負責將實體對象 (比如前面章節說的Person)插入數據庫和修改數據庫中對應的記錄。
2 、OrmRemover:負責根據實體對象,刪除指定的記錄;
3、OrmReader: 負責根據實體對象,讀取指定的記錄;
上面就是MiniORM的3個主要類。下面我們就詳細地根據前面的描述一 步步構造她。我們這裡還是以前面說的Person為例進行說明。
通過本系 列第一章,我們知道,對象不但存在繼承關系,特別在實際的應用中還存在包含 關系,比如一個Person包含兩個Hand(手)類,包含一個Head(頭)類等,我們 的Person在數據庫中應該有一個ID,為了更加方便使用和討論,此ID在MiniORM 中是一個int以及自動增長類型(ID INDENTITY(1,1))。這些都是我們的 MiniORM應該考慮的范圍。
我們對我們的Person做修改:
[DataObjectAttribute("Person")]
public class Person
{
private int _ID;
private string _Name;
private int _Age;
private string _Sex;
private Head _Head;
private Hand _LeftHand;
private Hand _RightHand;
public int ID
{
get { return _ID; }
set { _ID = value; }
}
public Head Head
{
get { return _Head; }
set { _Head = value; }
}
public Hand LeftHand
{
get { return _LeftHand; }
set { _LeftHand = value; }
}
public Hand RightHand
{
get { return _RightHand; }
set { _RightHand = value; }
}
[DataFieldAttribute("name", "NvarChar")]
public string Name
{
get { return this._Name; }
set { this._Name = value; }
}
[DataFieldAttribute("age", "int")]
public int Age
{
get { return this._Age; }
set { this._Age = value; }
}
[DataFieldAttribute ("sex", "NvarChar")]
public string Sex
{
get { return this._Sex; }
set { this._Sex = value; }
}
}
你可能又發現了一個 問題,就是在我們修改後的Person中,增加了LeftHand,RightHand以及Head, 可是這三個都屬於類啊,這個怎麼能夠保存到數據庫中呢?並且使用我們前面的 DataFieldAttribute是沒有辦法描述的啊。另外還增加了個ID,又怎麼來標志這 個是自動增長的int型PK呢?當然了能夠到這裡你就發現這些問題那是相當的不 錯了。如果前面就動手的人,估計考慮的還是修改我們的DataFieldAttribute讓 它能夠對這些東西進行區別。比如在DataFieldAttribute中再增加一個屬性用於 區別哪個是ID屬性,哪個是對象類型(比如Hand)屬性。這當然是好的,只不過 這樣做導致我們的代碼極其丑陋。最好的辦法還是另外增加一個Attribute。當 然了,我是為了更加方便的構造SQL語句,我做的不是很好。
1、怎麼表 示實體類對應的數據庫表的PK和FK?
為了更方便的實現,MiniORM中標志 一個實體類的PK和FK都是在DataObjectAttribute中來做(其實最好的辦法還是 另外增加個比如PKAttribute和FKAttribute,不過這個留給其它人去做吧)。如 下,DataObjectAttribute第一個參數表示對應的數據庫表,第二個參數表示PK ,第三個參數表示FK:
[DataObjectAttribute ("Person", "ID", "")]
2、怎麼標志字段是Indentity (自動增長)?
public class Person
{
......
}
在DataFieldAttribute中增加了個屬性,用於標志某個 字段是否自動增長的字段。這些都是我個人懶做的,其中,第二個參數標志ID是 Identity類型
[DataFieldAttribute("ID", true)]
public int ID
{
get { return _ID; }
set { _ID = value; }
}
3、怎樣標志字段是類對象(比如Person中的 Hand,當然復雜點的對象,可能包含子對象列表)?
由於MiniORM提供的 是一個類似框架的東西,所以不應該受到實體類的限制,所以對於類對象字段, 我們應該能夠描述此對象所在的程序集,命名空間,類名,這樣我們才可以運行 時創建該對象。
public class SubDataObjectAttribute : Attribute
{
private SubDataObjectFieldType _FieldType;
private string _AssemblyName;
private string _NamespaceName;
private string _ClassName;
public SubDataObjectAttribute(SubDataObjectFieldType fieldtype, string assemblyname, string namespacename, string classname)
{
this._FieldType = fieldtype;
this._AssemblyName = assemblyname;
this._NamespaceName = namespacename;
this._ClassName = classname;
}
/// <summary>
/// 本記錄對應的FieldType
/// </summary>
public SubDataObjectFieldType FieldType
{
get { return _FieldType; }
}
/// <summary>
/// 本記錄對應的AssemblyName
/// </summary>
public string AssemblyName
{
get { return _AssemblyName; }
}
/// <summary>
/// 本記錄對應的NamespaceName
/// </summary>
public string NamespaceName
{
get { return _NamespaceName; }
}
/// <summary>
/// 本記錄對應的ClassName
/// </summary>
public string ClassName
{
get { return _ClassName; }
}
}
其中 SubDataObjectFieldType是一個枚舉類型,因為我們的子對象可能是單獨的對象 比如Person.Head也可能是一個列表(List)。所以我增加了這個枚舉類型,用 於做標志。
public enum SubDataObjectFieldType
當然了,這裡的子對象列表可能是ArrayList,HashTable等等,你都可 以根據自己項目中實際使用到的類型來做相應的修改。
{
Object,
/// <summary>
/// 本字段屬於List類 型,直接遍歷
/// </summary>
List,
}
4、怎麼控制某個 字段在表中不能重復?
比如我們要控制Person.Name不能重復,如果你新 增的時候發現重復要提示。那我們也通過增加一個Attribute的形式來實現。這 個Attribute很簡單,沒有任何方法和屬性。
public class DataFieldNotDoubleAttribute : Attribute
{
}
5、 怎樣做事務處理?
事務處理是每個底層框架都應該考慮到的問題, 在.NET中我們有兩種方式來進行事務處理,一種是使用COM+,這是最好的方法, 不過性能上比較欠缺,另外這東西配置很麻煩,當你數據庫安裝在另外一太服務 器上的時候,往往出現無法使用的問題,我曾經就被這東西折騰夠嗆,所以我干 脆就不用他了,不過還是介紹下語法,通過使用TransactionScope就可以很好的 使用com+提供的事務處理,代碼相當的簡潔,優美,只可惜啊!天使的面孔,魔 鬼的心。
public void function1()
另外一種方法就是使 用SqlTransaction:
{
using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope (System.Transactions.TransactionScopeOption.Required))
{
function2();
}
}
public void function2()
{
using (System.Transactions.TransactionScope scope = new System.Transactions.TransactionScope (System.Transactions.TransactionScopeOption.Required))
{
//DoSomething();
}
}
using (SqlConnection conn = new SqlConnection(ConnectionStr))
不過遺憾的是 這種方式不能實現事務嵌套,所以只能通過將trans作為參數進行傳遞來實現事 務處理。
{
conn.Open();
SqlTransaction trans = conn.BeginTransaction();
//DoSomething();
trans.Commit();
}
經過上面一系列的修改後,我們的Person成了什麼樣子了?
[DataObjectAttribute("Person", "ID", "")]
public class Person
{
private int _ID;
private string _Name;
private int _Age;
private string _Sex;
private Head _Head;
private Hand _LeftHand;
private Hand _RightHand;
[DataFieldAttribute("ID", true)]
public int ID
{
get { return _ID; }
set { _ID = value; }
}
[SubDataObjectAttribute (SubDataObjectFieldType.Object, "Person", "Person", "Head")]
public Head Head
{
get { return _Head; }
set { _Head = value; }
}
[SubDataObjectAttribute (SubDataObjectFieldType.Object, "Person", "Person", "Hand")]
public Hand LeftHand
{
get { return _LeftHand; }
set { _LeftHand = value; }
}
[SubDataObjectAttribute(SubDataObjectFieldType.Object, "Person", "Person", "Hand")]
public Hand RightHand
{
get { return _RightHand; }
set { _RightHand = value; }
}
[DataFieldAttribute("name", "NvarChar")]
public string Name
{
get { return this._Name; }
set { this._Name = value; }
}
[DataFieldAttribute("age", "int")]
public int Age
{
get { return this._Age; }
set { this._Age = value; }
}
[DataFieldAttribute ("sex", "NvarChar")]
public string Sex
{
get { return this._Sex; }
set { this._Sex = value; }
}
}
當然了對於 Person這樣的實體類,我們完全可以自己寫代碼自動生成工具來弄,然後再做很 小的修改就可以了,這樣的工具實現簡單,我就不討論了。
好了,關於 我的MiniORM我就討論到這裡了,其它的請看代碼吧。
ORM雖然是好東西 ,但是也存在很多方面的不足,首先我們能夠做到的是將大部分的數據庫操作交 個ORM來做。另外少部分還是需要我們自己寫SQL的。單大部分的工作的分離可以 為我們節約大量的時間(也就是所謂的20/80原則,80%的教給ORM來處理,20%的 自己做,當然很好了)。另外通過將這些相同的流程教給ORM來處理,可以避免 很多的疏忽導致的失誤(比如不小心把某個Insert,Update,Delete語句弄錯了 什麼的)。
最主要的缺點當然是性能問題,特別是我的MiniORM,全部采 用反射來獲取映射規則,從而導致性能上更多的下降,不過我們了解方法以後是 很容易通過動態生成代碼,動態編譯的方式來減少這部分的性能損失的。另外某 些部分的代碼顯得有些臃腫,特別是把判斷是否Indentity這樣的代碼放 DataFieldAttribute中來處理(這個完全可以象DataFieldNotDoubleAttribute 一分開處理的樣)等等。