NHibernate繼承的三種策略 - 跟著Wade學之課後複習

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如下

image

從資料上可看出,當添加父類別資料時,識別欄位會塞入我們設定的Parent

若是子類別的話,則是Children

image

而在查詢的時候,NH就會自動將識別的字串加到Where條件中

image

 

 

 

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"));
    }
}

image

由圖可以看出,生成兩個Table,但Children Table中並沒有包含[Name]的欄位

image

也可看出兩者有一對一的關聯存在。

 

對Children下Query時,會做Join的動作

image

 

 

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,如果用了會報出下面的錯誤

image

生成的Table

image

可以生成兩個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的話要裝一下喔。

範例專案