NHibernate繼承的三種策略 - 跟著Wade學之課後複習
最近很紅火的跟著Wade學習MVC & NHibernate的這股風潮
我也很順勢的搭上了。
但由於他的授課內容是在實作一個完整的架構,因此在於學習NHibernate的焦點上
多少有些被轉移掉,因此我打算利用課後複習的方式,用自己的方法慢慢將
一些較細部的說明及設定補上,讓有心跟著Wade學習的人可以更清楚的了解
整個教學內容的精闢之處。
今天補上Day 3的NHibernate的繼承策略
主要的繼承策略有三種:
1. Table per class hierarchy
2. Table per subclass
3. Table per concrete class
今天的範例Model只有兩個簡單Class
public class Parent
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Children : Parent
{
public virtual string Phone { get; set; }
}
我們可以來看看這兩個Class在不同的Mapping策略下,所產生的Table有什麼不一樣
Table per class hierarchy
這種繼承在Class上雖然有階層關係,但只會產生出一個Table,並利用一個欄位當作識別項。
Mapping的方式為:
public class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Id(p => p.Id, s => s.Generator(Generators.Identity));
Property(p => p.Name);
//定義識別的欄位名稱
Discriminator(map => map.Column("ModelType"));
//父類別在此欄位的值
DiscriminatorValue("Parent");
}
}
//使用SubclassMapping
public class ChildrenMapping : SubclassMapping<Children>
{
public ChildrenMapping()
{
Property(p => p.Phone);
//子類別在此欄位的值
DiscriminatorValue("Children");
}
}
跑單元測試
[TestMethod]
public void TestMethod()
{
using (var session = NHibernateFactory.Session.OpenSession())
using (var trans = session.BeginTransaction())
{
session.Save(new Parent { Name = "Jerry Parent" });
session.Save(new Children { Name = "Jerry Children", Phone = "0936" });
var model = session.QueryOver<Children>().Where(p => p.Id == 2).SingleOrDefault();
trans.Commit();
}
}
其生出來的Table如下
從資料上可看出,當添加父類別資料時,識別欄位會塞入我們設定的Parent
若是子類別的話,則是Children
而在查詢的時候,NH就會自動將識別的字串加到Where條件中
Table per subclass
這種繼承策略在父與子Class都會各產生一張Table,但子Table不會包含父Table的欄位,而
真正在撈取子Table的資料時,會在SQL中join 父Table。
public class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Id(p => p.Id, s => { s.Generator(Generators.Identity); s.Column("ParentId"); });
Property(p => p.Name);
}
}
//使用JoinedSubclassMapping
public class ChildrenMapping : JoinedSubclassMapping<Children>
{
public ChildrenMapping()
{
Property(p => p.Phone);
//很容易理解的,因為要做Join,因此必須給他一個可以關聯的欄位
//父類別的Id欄位我們命名為"ParentId",因此這邊也取這個名稱
Key(p => p.Column("ParentId"));
}
}
由圖可以看出,生成兩個Table,但Children Table中並沒有包含[Name]的欄位
也可看出兩者有一對一的關聯存在。
對Children下Query時,會做Join的動作
Table per concrete class
最後這一種,也是父子都會個建一個Table,但子Table會擁有父類別的所有欄位
public class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
//注意這邊的PK產生方式,不能使用Generators.Identity
Id(p => p.Id, s => { s.Generator(Generators.HighLow); });
Property(p => p.Name);
}
}
//使用UnionSubclassMapping
public class ChildrenMapping : UnionSubclassMapping<Children>
{
public ChildrenMapping()
{
//子類別的Mapping,只要設子類別的屬性就好
//父類別的Mapping設定會自動套用
Property(p => p.Phone);
}
}
這邊特別的一點是,PK產生沒辦法用Identity,如果用了會報出下面的錯誤
生成的Table
可以生成兩個Table,並且Children擁有所有的欄位。
但我覺得Identity不能用實在是不方便,因此還是使用ClassMapping比較好,
只是要幫Children Class的所有屬性Mapping都補上就是了。
另外提供測試時的一些小設定,如果懶得加裝Log及附掛事件的話,可以使用下面的設定
configuration.DataBaseIntegration(c =>
{
c.Dialect<NHibernate.Dialect.SQLiteDialect>();
c.ConnectionString = String.Format(@"Data Source={0}Test.db", Path.GetTempPath());
c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
c.SchemaAction = SchemaAutoAction.Create;
c.IsolationLevel = IsolationLevel.ReadCommitted;
//將下面屬性設為true,就可在測試時看到產生的SQL指令
c.LogSqlInConsole = true;
//加入一些簡單的註釋
c.AutoCommentSql = true;
});
這樣在測試的時候,就可在測試結果詳細資料中看到所產生的SQL指令,
方便理解各種不同的設定會產生何種不同結果
以下提供範例所用的所有內容,如果未在本機上安裝過SQLite的話要裝一下喔。