ORM中的繼承關系映射全解——單表繼承體系、一實體一具體表、一實體一擴展表、接口映射
本文涉及的內容包括:
1.單表繼承體系
2.一實體一具體表
3.一實體一擴展表
4.接口實現映射vs基類繼承映射
1.單表繼承體系
所謂單表繼承體系就是用一張數據庫表存儲整個繼承體系中的所有實體的數據。單表繼承體系適合那種繼承體系中實體數目相對較少,總記錄數相對較少,子類對父類的屬性擴展也相對較少的情形。
單表繼承體系優點是讀/寫繼承體系中的每個實體的數據,都只需操作一張表,性能較好,並且,新增繼承類,或擴展實體屬性都只需要增減一張表的字段就可以了,易於維護;主要缺點是,因為所有的實體共享一張表,表中會有比較多的NULL字段值的數據,浪費了一些存儲空間,同時,如果記錄數過多,表就會更龐大,也會影響表的讀寫性能。
簡單單表繼承體系
讓我們先看一個假象的例子:
[Table("AllInOneTable")]
public interface Parent : IEntity
{
[PrimaryKey]
int ID { get; }
string Name { get; set; }
}
[Table("AllInOneTable")]
public interface AnotherParent : IEntity
{
[PrimaryKey]
int ID { get; }
int Age { get; set; }
}
[Table("AllInOneTable")]
public interface Child : Parent, AnotherParent
{
[PrimaryKey]
new int ID { get; set; }
DateTime Birthday { get; set; }
}
我們可以看到,在上例中,我們定義了兩個基實體Parent和AnotherParent,Child實體同時從兩個基類繼承。注意,代碼中加粗的行,如果多個不同的基接口包含相同名稱的屬性,代碼會編譯失敗,此時,需要像這樣使用new關鍵字來避免編譯失敗。
這裡,我們采用的是單表繼承體系方式,注意每個實體都映射到AllInOneTable這個表,只不過對每個實體來說,只使用了AllInOneTable表的部分字段。
但是,以這樣的簡單方式定義單表繼承時,因為從表中讀數據時無法知道一行數據真正對應的是哪一個子類,所以,實際情況下,一般我們都要附加一些查詢條件和字段默認值。
帶附加條件的單表繼承體系
采用單表繼承體系方案時,繼承體系中的不同子類不僅僅擴展父類的屬性,肯定還會附帶一些字段查詢條件和默認值。這裡會用到NBear.Common.TableAttribute的AdditionalWhere和AdditionalInsert屬性,關於這兩個屬性的詳細用法請參考表映射。
請看下面的代碼:
public interface Message : IEntity
{
[PrimaryKey]
int ID { get; }
string Title { get; set; }
string Content { get; set; }
DateTime UpdateTime { get; set; }
}
[Table("Message", AdditionalInsert = "[MessageType] = 1", AdditionalWhere = "[MessageType] = 1")]
public interface CommonMessage : Message
{
int UserID { get; set; }
}
[Table("Message", AdditionalInsert = "[MessageType] = 2", AdditionalWhere = "[MessageType] = 2")]
public interface SpecialMessage : Message
{
int GroupID { get; set; }
}
這裡我們實際要持久化的是兩個實體CommonMessage和SpecialMessage,他們有一個共同的抽象父類Message,父類作為一個抽象類不會被直接使用。我們將整個繼承體系存於Message數據表。包含CommonMessage和SpecialMessage的所有屬性。但是,就像我們在上面的假象示例中所說的,如果直接查詢Message表,返回CommonMessage對應的字段數據,那麼連SpecialMessage插入的那些數據也會被返回,反之亦然,這顯然是不符合要求的。
因此,我們需要定義附加的查詢條件和插入默認值,即為Message表增加一個MessageType字段,該字段值為1的數據代表CommonMessage,值為2代表該行數據是SpecialMessage,如上面的代碼中定義AdditionalWhere和AdditionalInsert後,查詢CommonMessage或SpecialMessae時,就只會返回真正對應到他們的MessageType值的記錄了;當插入數據時,為CommonMessage和SpecialMessage,框架也會自動為其設置必要的MessageType默認值。