需要在一個(gè)已經(jīng)在運(yùn)行中的系統(tǒng)中引入一個(gè)(新增的)父類(lèi)的情況很常見(jiàn)驼壶。
假設(shè)在這個(gè)系統(tǒng)中我們?cè)染鸵呀?jīng)使用了 ORM 框架水慨,而使用的 ORM 框架可能支持繼承映射特性间校。繼承映射大致有三種方式:
- TPH:Table Per Hierarchy
- TPT:Table Per Type
- TPC:Table Per Concrete Type
如何在這幾種方式中進(jìn)行選擇肛跌?對(duì)于一個(gè)已經(jīng)在運(yùn)行的系統(tǒng)來(lái)說(shuō)刊棕,其中有一個(gè)重要因素需要考慮:怎么減少對(duì)數(shù)據(jù)庫(kù)模型(Schema)的修改个曙?
顯然锈嫩,使用 TPC 方式對(duì)數(shù)據(jù)庫(kù)模型(Schema)的影響最小受楼。
不要小看這一點(diǎn)。有人說(shuō)呼寸,SQL 數(shù)據(jù)庫(kù)之所以取得巨大的成功艳汽,是因?yàn)樗峁┝艘粋€(gè)標(biāo)準(zhǔn)的集成機(jī)制。往往很多代碼对雪、系統(tǒng)都是和 SQL 數(shù)據(jù)庫(kù)模型耦合在一起的河狐,對(duì)數(shù)據(jù)庫(kù) Schema 的修改往往牽一發(fā)而動(dòng)全身。對(duì)于已經(jīng)在使用的系統(tǒng)瑟捣,重構(gòu)還是一小步一小步地來(lái)好一些馋艺。下面我們就先看看怎么用 TPC 的方式來(lái)搞吧。
我們假設(shè)現(xiàn)在系統(tǒng)中有兩個(gè)類(lèi)迈套,一個(gè)是 Store(門(mén)店)捐祠,一個(gè)是 Agency(代理)。
其中 Store 的代碼如下:
using System;
namespace NHibernateTest.Domain.Model
{
public class Store
{
public Store ()
{
}
public virtual int StoreId { get; set; }
public virtual string StoreName { get; set; }
public virtual string Location { get; set; }
}
}
另外一個(gè)類(lèi) Agency 的代碼如下:
using System;
namespace NHibernateTest.Domain.Model
{
public class Agency
{
public Agency ()
{
}
public virtual int AgencyId { get; set; }
public virtual string AgencyName { get; set; }
public virtual string Location { get; set; }
}
}
假設(shè)我們的系統(tǒng)的使用了 NHiberante 作為 ORM 框架桑李。Store 的 OR 映射文件:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateTest"
namespace="NHibernateTest.Domain.Model">
<class name="Store" table="Stores">
<id name="StoreId" column="Id">
<generator class="native" />
</id>
<property name="StoreName" />
<property name="Location" />
</class>
</hibernate-mapping>
Agency 的映射文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateTest"
namespace="NHibernateTest.Domain.Model">
<class name="Agency" table="Agencies">
<id name="AgencyId" column="Id">
<generator class="native" />
</id>
<property name="AgencyName" />
<property name="Location" />
</class>
</hibernate-mapping>
現(xiàn)在踱蛀,我們發(fā)現(xiàn)需要引入一個(gè)父類(lèi) Organization(組織)。
至于為什么要引入贵白?這里不多解釋?zhuān)?qǐng)自行腦補(bǔ)吧星岗。
如果使用 TPC 方式,對(duì)代碼的修改非常簡(jiǎn)單戒洼。
Organization 的代碼:
using System;
namespace NHibernateTest.Domain.Model
{
public abstract class Organization
{
static Random rand = new Random ();
public static int NextOrganizationId ()
{
return rand.Next ();
}
public Organization ()
{
}
public virtual int OrganizationId { get; set; }
public virtual string Name { get; set; }
public virtual string Location { get; set; }
}
}
對(duì) Store 的修改如下:
using System;
namespace NHibernateTest.Domain.Model
{
public class Store : Organization
{
public Store ()
{
}
// public virtual int StoreId { get; set; }
//
// public virtual string StoreName { get; set; }
//
// public virtual string Location { get; set; }
public virtual int StoreId {
get { return OrganizationId; }
set { OrganizationId = value; }
}
public virtual string StoreName {
get { return Name; }
set { Name = value; }
}
}
}
對(duì) Agency 的修改如下:
using System;
namespace NHibernateTest.Domain.Model
{
public class Agency : Organization
{
public Agency ()
{
}
// public virtual int AgencyId { get; set; }
//
// public virtual string AgencyName { get; set; }
//
// public virtual string Location { get; set; }
public virtual int AgencyId {
get{ return OrganizationId; }
set{ OrganizationId = value; }
}
public virtual string AgencyName {
get { return Name; }
set { Name = value; }
}
}
}
原來(lái) Store 和 Agency 的映射文件可以干掉了。新的 Organization 的映射文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateTest"
namespace="NHibernateTest.Domain.Model">
<class name="Organization" abstract="true">
<id name="OrganizationId" column="Id">
<generator class="assigned" />
</id>
<union-subclass name="Agency"
table="Agencies">
<property name="Name" column="AgencyName"/>
<property name="Location" />
</union-subclass>
<union-subclass name="Store"
table="Stores">
<property name="Name" column="StoreName"/>
<property name="Location" />
</union-subclass>
</class>
</hibernate-mapping>
測(cè)試代碼片段如下:
public static void TestOrgs ()
{
using (ISession session = _sessionFactory.OpenSession ())
using (ITransaction transaction = session.BeginTransaction ()) {
Store s = new Store ();
//***********
s.OrganizationId = Organization.NextOrganizationId ();
//
s.StoreName = Guid.NewGuid ().ToString ();
s.Location = Guid.NewGuid ().ToString ();
session.Save (s);
Agency a = new Agency ();
//***********
a.OrganizationId = Organization.NextOrganizationId ();
//
a.AgencyName = Guid.NewGuid ().ToString ();
a.Location = Guid.NewGuid ().ToString ();
session.Save (a);
transaction.Commit ();
}
}
上面假設(shè)原來(lái) Store 和 Agency 兩個(gè)實(shí)體在數(shù)據(jù)庫(kù)中的主鍵列名都是 Id允华,分別映射到代碼中 StoreId 和 AgencyId圈浇。這種情況下,我們看看使用 TPC 方式為它們引入一個(gè)共同的父類(lèi)靴寂,我們做了哪些修改:
-
修改了 Id 的產(chǎn)生機(jī)制磷蜀。
這里出于演示的目的,將 Organization(Store 和 Agency)的 Id 改成了 assigned 方式百炬,并在 Organization 的代碼中寫(xiě)了一個(gè)演示性的 Id 生成方法 NextOrganizationId(隨機(jī)產(chǎn)生一個(gè)整數(shù)作為 Id褐隆,不要用在正式的生產(chǎn)代碼中)。其實(shí)還有其他的 Id 產(chǎn)生機(jī)制可以選的剖踊;
新增了一個(gè) Organization 抽象父類(lèi)庶弃。將 Store 和 Agency 概念相同的屬性提升到這個(gè)父類(lèi)中;
修改了 Store 和 Agency 的代碼德澈,讓它們分別繼承 Organization歇攻,并將它們?cè)械囊恍傩栽L(fǎng)問(wèn)操作委托給父類(lèi)實(shí)現(xiàn)。
除此之外梆造,就沒(méi)有別的了缴守。特別需要注意的是:數(shù)據(jù)庫(kù) Schema 可能幾乎沒(méi)有做修改。這里說(shuō)可能幾乎,是因?yàn)椋涸瓉?lái)的 Id 產(chǎn)生器是 native 的屡穗,而對(duì)不同的底層數(shù)據(jù)庫(kù)來(lái)著贴捡,native 的實(shí)現(xiàn)機(jī)制可能是不一樣的。比如 MS SQL Server 和 MySQL 默認(rèn)是使用了自增類(lèi)型的字段村砂。這時(shí)候就需要把 Id 字段的自增屬性去掉烂斋。
當(dāng)然,這里說(shuō)的只是數(shù)據(jù)庫(kù)的 Schema (幾乎)不變箍镜,但是系統(tǒng)遺留的已有數(shù)據(jù)(Store 和 Agency)可能存在 Id 沖突源祈,所以數(shù)據(jù)遷移還是要做的。
還有色迂,如果原來(lái)的(Store 和 Agency 對(duì)應(yīng)的)兩個(gè)數(shù)據(jù)表中香缺,Id 的字段名不是 Id,而是 StoreId 和 AgencyId歇僧,那 Schema 可能就需要改了图张。
以上是使用 NHiberante 舉例,EF 應(yīng)該也差不多诈悍。關(guān)于 EF 的繼承映射支持祸轮,可以自行 Google。也可以看看這篇文章:
http://www.cnblogs.com/oppoic/p/ef_tph_tpt_tpc_inheritance.html