使用 ORM 給系統的某些實體引入一個父類

需要在一個已經在運行中的系統中引入一個(新增的)父類的情況很常見。

假設在這個系統中我們原先就已經使用了 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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容