NHibernate - 更改子类型

7

如何在NHibernate中更改行的子类型?例如,如果我有一个Customer实体和TierOneCustomer的子类,我需要将Customer更改为TierOneCustomer,但是TierOneCustomer应该具有与原始Customer实体相同的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)并设置相关类型的适当列即可。我无法在NHibernate中找到解决方案,因此希望得到任何指导。
我也在思考是否正确地考虑了这种情况,但在走这条路之前,我想确保首先按照上述描述的方式实际上是可能的。如果不行,我几乎肯定会考虑更改模型。
4个回答

11
简短回答是可以,您可以使用native SQL更改特定行的鉴别器值。然而,我认为NHibernate并不打算以这种方式工作,因为鉴别器通常对Java层“不可见”,它的值应根据持久化对象的类进行初始设置,并且永远不会更改。建议寻找更清晰的方法。从对象模型的角度来看,您正在尝试将超类对象转换为其子类类型,同时不更改其持久化实例的标识,这就是冲突所在(转换后的对象实际上不应该是同一物体)。两种替代方法是:创建一个基于原始Customer对象信息的TierOneCustomer的新实例,然后删除原始对象;或者改变您的方法,使对象类型(鉴别器)不需要更改。与其依赖子类来区分TierOneCustomer和Customer,不如使用您可以随时自由修改的属性,即Customer.Tier = 1。以下是Hibernate论坛上的一些相关讨论,可能会引起您的兴趣:
  1. 在Hibernate中可以更新鉴别器列吗
  2. 表-类问题:鉴别器和属性
  3. 将持久化实例转换为子类

是的,我想我会重构出层次结构,并且可能选择简单属性方法。非常好的答案,谢谢。 - Andy Whitfield

6
你做错了什么。
你试图改变一个对象的类型,但在.NET或Java中无法这样做,因为这根本没有意义。一个对象只有一个具体类型,从创建到销毁期间其具体类型不能更改(黑魔法除外)。为了实现你想要的,但使用你所设计的类层次结构,你需要销毁你想转换成一级客户对象的客户对象,创建一个新的一级客户对象,并将客户对象的所有相关属性复制到一级客户对象中。这是在面向对象语言中处理对象的方法和你的类层次结构。
显然,你所拥有的类层次结构对你来说并不起作用。在现实生活中,当消费者成为一级客户时,你不会销毁他们!因此,在对象上也不要这样做。相反,根据你需要实现的场景,提出一个合理的类层次结构。你的使用场景包括:
- 先前不是一级状态的客户现在成为一级状态的客户。
这意味着你需要一个能够准确捕捉这种情况的类层次结构。一个提示是,你应该优先使用组合而非继承。这意味着最好有一个名为 IsTierOne 的属性,或者一个名为 DiscountStrategy 的属性,具体取决于哪个更好。
NHibernate(以及Java中的Hibernate)的整个目的是使数据库不可见。让你可以本地处理对象,并且数据库在幕后神奇地使你的对象持久化。NHibernate将让你直接处理数据库,但这并不是NHibernate构建的场景类型。

我喜欢这个答案,但感觉David的措辞略好一些,所以我接受了他的。不过,非常感谢你的好回答 - 我已经点赞了。 - Andy Whitfield

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的现有记录上作为对象属性,以便将来对该类型的检索是作为新对象。属性上需要设置setter,因为据我所知,NHibernate无法告知属性是只读的(因此对于数据库而言是“只写”);在NHibernate的世界中,如果将某些内容写入数据库,为什么不希望将其取回呢?
最近我使用了这种模式,允许用户更改“旅游”的基本类型,实际上是管理实际“旅游”安排(单个数字“访问”,以确保客户现场设备正常工作)。虽然它们都是“旅游计划”,并且需要按照这样的方式进行收集列表/队列等,但不同类型的计划需要非常不同的数据和非常不同的处理,需要与OP拥有的类似数据结构。因此,我完全理解OP希望以大幅度不同的方式处理TierOneCustomer,同时最小化数据层面影响的愿望,所以这里提供给你。

1

如果您是在离线环境下进行操作(例如在数据库升级脚本中),只需使用SQL并确保一致性即可。

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

如果您保存了ID并使用它来再次访问客户(例如在URL中),请考虑创建一个新字段,其中包含此业务键的令牌。由于它不是ID,因此很容易创建一个新实体实例并复制令牌(您可能需要从旧实例中删除令牌)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接