需要在一個已經在運行中的系統中引入一個(新增的)父類的情況很常見。
假設在這個系統中我們原先就已經使用了 ORM 框架,而使用的 ORM 框架可能支持繼承映射特性。繼承映射大致有三種方式:
- TPH:Table Per Hierarchy
- TPT:Table Per Type
- TPC:Table Per Concrete Type
如何在這幾種方式中進行選擇?對于一個已經在運行的系統來說,其中有一個重要因素需要考慮:怎么減少對數據庫模型(Schema)的修改?
顯然,使用 TPC 方式對數據庫模型(Schema)的影響最小。
不要小看這一點。有人說,SQL 數據庫之所以取得巨大的成功,是因為它提供了一個標準的集成機制。往往很多代碼、系統都是和 SQL 數據庫模型耦合在一起的,對數據庫 Schema 的修改往往牽一發而動全身。對于已經在使用的系統,重構還是一小步一小步地來好一些。下面我們就先看看怎么用 TPC 的方式來搞吧。
我們假設現在系統中有兩個類,一個是 Store(門店),一個是 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; }
}
}
另外一個類 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; }
}
}
假設我們的系統的使用了 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>
現在,我們發現需要引入一個父類 Organization(組織)。
至于為什么要引入?這里不多解釋,請自行腦補吧。
如果使用 TPC 方式,對代碼的修改非常簡單。
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; }
}
}
對 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; }
}
}
}
對 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; }
}
}
}
原來 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>
測試代碼片段如下:
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 ();
}
}
上面假設原來 Store 和 Agency 兩個實體在數據庫中的主鍵列名都是 Id,分別映射到代碼中 StoreId 和 AgencyId。這種情況下,我們看看使用 TPC 方式為它們引入一個共同的父類,我們做了哪些修改:
-
修改了 Id 的產生機制。
這里出于演示的目的,將 Organization(Store 和 Agency)的 Id 改成了 assigned 方式,并在 Organization 的代碼中寫了一個演示性的 Id 生成方法 NextOrganizationId(隨機產生一個整數作為 Id,不要用在正式的生產代碼中)。其實還有其他的 Id 產生機制可以選的;
新增了一個 Organization 抽象父類。將 Store 和 Agency 概念相同的屬性提升到這個父類中;
修改了 Store 和 Agency 的代碼,讓它們分別繼承 Organization,并將它們原有的一些屬性訪問操作委托給父類實現。
除此之外,就沒有別的了。特別需要注意的是:數據庫 Schema 可能幾乎沒有做修改。這里說可能幾乎,是因為:原來的 Id 產生器是 native 的,而對不同的底層數據庫來著,native 的實現機制可能是不一樣的。比如 MS SQL Server 和 MySQL 默認是使用了自增類型的字段。這時候就需要把 Id 字段的自增屬性去掉。
當然,這里說的只是數據庫的 Schema (幾乎)不變,但是系統遺留的已有數據(Store 和 Agency)可能存在 Id 沖突,所以數據遷移還是要做的。
還有,如果原來的(Store 和 Agency 對應的)兩個數據表中,Id 的字段名不是 Id,而是 StoreId 和 AgencyId,那 Schema 可能就需要改了。
以上是使用 NHiberante 舉例,EF 應該也差不多。關于 EF 的繼承映射支持,可以自行 Google。也可以看看這篇文章:
http://www.cnblogs.com/oppoic/p/ef_tph_tpt_tpc_inheritance.html