7

你如何去改变 NHibernate 中一行的子类型?例如,如果我有一个客户实体和 TierOneCustomer 的子类,我需要将客户更改为 TierOneCustomer,但 TierOneCustomer 应该具有与原始客户实体相同的 ID (PK)。

映射看起来像这样:

<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,只需对鉴别器 (CustomerType) 进行 sql 更新并设置与类型相关的适当列。我无法在 NHibernate 中找到解决方案,因此不胜感激。

考虑到这个用例,我也在考虑模型是否正确,但在我走这条路之前,我想首先确保按照上述方式进行操作实际上是可能的。如果没有,我几乎肯定会考虑改变模型。

4

4 回答 4

11

简短的回答是肯定的,您可以使用本机 SQL更改特定行的鉴别器值。

但是,我不认为 N​​Hibernate 打算以这种方式工作,因为鉴别器通常对 Java 层“不可见”,它的值应该最初根据持久对象的类设置并且永远不会改变。

我建议研究一种更清洁的方法。从对象模型的角度来看,您正在尝试将超类对象转换为其子类类型之一,同时不更改其持久实例的标识,这就是冲突所在(转换后的对象实际上不应该是一样的东西)。两种替代方法是:

  • 根据原Customer对象中的信息新建一个TierOneCustomer实例,然后删除原对象。如果您依赖客户的主键进行检索,则需要注意新的 PK。

或者

  • 改变你的方法,这样对象类型(鉴别器)就不需要改变了。您可以使用可以随时自由修改的属性,即 Customer.Tier = 1,而不是依靠子类来区分 TierOneCustomer 和 Customer。

以下是 Hibernate 论坛上可能感兴趣的一些相关讨论:

  1. 我们可以更新 Hibernate 中的鉴别器列吗
  2. Table-per-Class 问题:判别器和属性
  3. 将持久化实例转换为子类
于 2009-01-25T23:32:22.750 回答
6

你做错了什么。

您要做的是更改对象的类型。你不能在 .NET 或 Java 中做到这一点。这根本没有意义。一个对象只有一种具体类型,从创建对象到销毁对象(尽管有黑魔法),它的具体类型不能更改。为了完成您想要做的事情,但是使用您布置的类层次结构,您必须销毁要变成一级客户对象的客户对象,创建一个新的一级客户对象,并将所有相关属性从客户对象复制到第一层客户对象。这就是你如何在面向对象的语言中使用你的类层次结构来处理对象。

显然,您拥有的类层次结构不适合您。当他们成为一级客户时,您不会破坏现实生活中的客户!所以也不要用对象来做。相反,考虑到您需要实现的场景,提出一个有意义的类层次结构。您的使用场景包括:

  • 以前不是一级状态的客户现在成为一级状态。

这意味着您需要一个可以准确捕获此场景的类层次结构。作为提示,您应该更喜欢组合而不是继承。IsTierOne这意味着,拥有一个名为 的属性或名为 的属性等可能是一个更好的主意DiscountStrategy,具体取决于最有效的方法。

NHibernate(以及 Java 的 Hibernate)的全部目的是使数据库不可见。为了让您能够原生地使用对象,数据库在幕后神奇地使您的对象持久化。NHibernate 将让您在本地使用数据库,但这不是 NHibernate 构建的场景类型。

于 2009-01-25T23:56:09.630 回答
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,同时尽量减少对数据层的影响,所以,来吧。

于 2012-08-24T23:55:56.440 回答
1

如果您是离线执行(例如在数据库升级脚本中),只需使用 SQL 并自己确保一致性。

如果这是您计划在应用程序运行时发生的事情,我认为您的要求是错误的,就像为不同的对象保持相同的指针地址是错误的一样。

如果您保存 ID 并使用它再次访问客户(例如在 URL 中),请考虑创建一个包含此标记的新字段,该标记将作为业务密钥。因为它不是 ID,所以很容易创建一个新的实体实例并复制令牌(您可能需要从旧的令牌中删除该令牌)。

于 2009-01-25T23:38:48.883 回答