2009-01-25 71 views
5

如何在NHibernate中更改行的子類型?例如,如果我有一個客戶實體和TierOneCustomer的子類,我有一個情況是需要將客戶更改爲TierOneCustomer,但是TierOneCustomer應該具有與原始客戶實體相同的Id(PK)。NHibernate - 更改子類型

映射看起來是這樣的:

<class name="Customer" table="SiteCustomer" discriminator-value="C"> 
    <id name="Id" column="Id" type="Int64"> 
    <generator class="identity" /> 
    </id> 
    <discriminator column="CustomerType" /> 
    ... properties snipped ... 

    <subclass name="TierOneCustomer" discriminator-value="P"> 
    ... more properties ... 
    </subclass> 
</class> 

我使用的每一個類,表層次模型,因此使用純SQL,它會只是一個的一個SQL更新的事鑑別器(CustomerType)並設置與該類型相關的適當列。我無法在NHibernate中找到解決方案,所以會感謝任何指針。

考慮到這個用例,我也在考慮模型是否正確,但在我走下那條路線之前,我想確保如上所述的實際上可能是首先實現的。如果沒有,我幾乎肯定會考慮改變模型。

回答

9

簡短回答是肯定的,您可以使用native SQL更改特定行的鑑別值。

但是,我不認爲NHibernate是用這種方式工作的,因爲鑑別器通常對Java層是「不可見的」,它的值應該根據持久化對象的類別初始設置,從未改變。

我建議尋找一種更清潔的方法。從對象模型的角度來看,您試圖將超類對象轉換爲它的一個子類類型,而不改變其持久實例的身份,這就是衝突的地方(轉換後的對象實際上不應該是一樣的東西)。兩種備選方法是:

  • 根據原始Customer對象中的信息創建TierOneCustomer的新實例,然後刪除原始對象。如果您依賴客戶的主鍵進行檢索,則需要記下新的PK。

  • 改變你的方法,以使對象類型(鑑別)並不需要改變。而不是依賴於一個子類,從客戶區分TierOneCustomer的,你可以使用,你可以在任何時候自由地修改屬性,即Customer.Tier = 1

這裏是Hibernate的論壇的一些相關討論,可能感興趣:

  1. Can we update the discriminator column in Hibernate
  2. Table-per-Class Problem: Discriminator and Property
  3. Converting a persisted instance into a subclass
+0

是的,我想我要重構層次結構,並可能選擇簡單的屬性方法。很好的回答,謝謝。 – 2009-01-26 09:41:01

1

如果您正在離線播放(例如在數據庫升級腳本中),只需使用SQL並確保一致性。

如果這是你計劃在應用程序運行時發生的事情,我認爲你的要求是錯誤的,就像保持不同對象的指針地址是錯誤的一樣。

如果保存的ID,並用它來再次訪問客戶(例如,在URL)考慮使包含此令牌,這將是業務重點的新領域。由於它不是ID,很容易創建一個新的實體實例並複製代碼(您可能需要從舊代碼中刪除代碼)。

5

你做錯了什麼。

你所要做的是改變一個對象的類型。你不能用.NET或Java來做到這一點。這根本沒有意義。一個對象只是一個具體的類型,它的具體類型不能從創建對象的時間到對象被銷燬的時間(黑魔法儘管)改變。爲了完成你想要做的事情,但是通過你所佈置的類層次結構,你必須銷燬你想要變成一級客戶對象的客戶對象,創建一個新的一級客戶對象,並將客戶對象中的所有相關屬性複製到第一層客戶對象。這就是你如何用對象,面向對象的語言和你的類層次結構來做到這一點。

很顯然,你的類層次結構是不是爲你工作。當他們成爲一線客戶時,您不會在現實生活中摧毀客戶!所以不要用對象來做。相反,考慮到您需要實施的場景,請提出一個合理的類層次結構。您的使用場景包括:

  • 以前不是一級狀態的客戶現在變爲一級狀態。

這意味着你需要一個類層次結構,可以準確地捕捉了這種情況。作爲一個暗示,你應該贊成構造而不是繼承。這意味着,最好有一個名爲IsTierOne的屬性或一個名爲DiscountStrategy等的屬性,具體取決於最佳效果。

的NHibernate的整個目的(和Hibernate的Java)是使數據庫無形。爲了讓你在本地處理對象,在數據庫中神奇地存在數據庫以使對象持久化。 NHibernate會讓你在本地處理數據庫,但這不是NHibernate爲之構建的場景類型。

+0

我很喜歡這個答案,但是覺得大衛的措辭稍微好一些,所以我接受了他的答案。儘管如此,我還是非常感謝這個好的答案 - 我已經投了票。 – 2009-01-26 09:44:44

2

這確實是晚了,但可能會使用到希望做類似的東西旁邊的人:

而其他的答案是正確的,你不應該變化在大多數情況下,鑑別,你可以純粹在NH的範圍內(沒有本機SQL),並且巧妙地使用映射的屬性。下面是它使用FluentNH要旨:

public enum CustomerType //not sure it's needed 
{ 
    Customer, 
    TierOneCustomer 
} 

public class Customer 
{ 
    //You should be able to use the Type name instead, 
    //but I know this enum-based approach works 
    public virtual CustomerType Type 
    { 
     get {return CustomerType.Customer;} 
     set {} //small code smell; setter exists, no error, but it doesn't do anything. 
    } 
    ... 
} 

public class TierOneCustomer:Customer 
{ 
    public override CustomerType Type {get {return CustomerType.TierOneCustomer;} set{}} 
    ... 
} 

public class CustomerMap:ClassMap<Customer> 
{ 
    public CustomerMap() 
    { 
     ... 
     DiscriminateSubClassesOnColumn<string>("CustomerType"); 
     DiscriminatorValue(CustomerType.Customer.ToString()); 
     //here's the magic; make the discriminator updatable 
     //"Not.Insert()" is required to prevent the discriminator column 
     //showing up twice in an insert statement 
     Map(x => x.Type).Column("CustomerType").Update().Not.Insert(); 
    } 
} 

public class TierOneCustomerMap:SubclassMap<TierOneCustomer> 
{ 
    public CustomerMap() 
    { 
     //same idea, different discriminator value 
     ... 
     DiscriminatorValue(CustomerType.TierOneCustomer.ToString()); 
     ... 
    } 
} 

最終的結果是,鑑別值是爲插入指定,並將其用於確定在檢索所述被實例化的類型,但這時如果一個不同的亞型具有相同的記錄Id被保存(就像記錄被克隆或從UI中解除綁定到新類型一樣),鑑別器值在現有記錄上以該ID作爲對象屬性更新,以便將來的這種類型的檢索是新對象。該屬性是必需的,因爲AFAIK NHibernate不能告訴屬性是隻讀的(並且因此對數據庫是「只寫」的)。在NHibernate的世界裏,如果你寫了一些東西給DB,爲什麼你不希望它回來?

我最近使用這種模式來允許用戶改變「遊覽」的基本類型,這實際上是一組規則來管理實際的「遊覽」(單個數字「拜訪」到客戶端現場設備以確保所有工作正常)。儘管它們都是「旅程安排」,並且需要在列表/隊列等中可收集,但不同類型的時間表需要非常不同的數據和非常不同的處理,因此需要與OP具有相似的數據結構。因此,我完全理解OP希望以一種完全不同的方式處理TierOneCustomer,同時最大限度地減少數據層的影響,所以,在這裏,你走了。