NHibernate - 更改子类型

时间:2009-01-25 21:10:52

标签: nhibernate class-hierarchy

你如何改变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>

我正在使用每个类的单表层次模型,所以使用plain-sql,只需要对鉴别器(CustomerType)进行sql更新,并设置与该类型相关的相应列。我无法在NHibernate中找到解决方案,所以非常感谢任何指针。

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

4 个答案:

答案 0 :(得分:10)

简短答案是肯定的,您可以使用native SQL更改特定行的鉴别值。

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

我建议采用更清洁的方法。从对象模型的角度来看,您尝试将超类对象转换为其子类类型之一,同时不更改其持久化实例的标识,这就是冲突的位置(转换后的对象实际上不应该是同一件事情)。两种替代方法是:

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

  • 更改您的方法,以便不需要更改对象类型(鉴别器)。您可以使用可以随时自由修改的属性,而不是依赖子类来区分TierOneCustomer和Customer,即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

答案 1 :(得分:6)

你做错了什么。

您要做的是更改对象的类型。你不能用.NET或Java做到这一点。这根本没有意义。对象只是一种具体类型,其具体类型不能从创建对象到对象被销毁时更改(尽管是黑魔法)。为了完成您要执行的操作,但是使用您布置的类层次结构,您必须销毁要转换为第一层客户对象的客户对象,创建新的第1层客户对象,并将客户对象中的所有相关属性复制到第一层客户对象。这就是你使用类层次结构在面向对象语言中使用对象的方法。

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

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

这意味着您需要一个能够准确捕获此场景的类层次结构。作为提示,你应该赞成合成而不是继承。这意味着,根据最佳效果,拥有名为IsTierOne的属性或名为DiscountStrategy的属性等可能是更好的主意。

NHibernate(和Hibernate for Java)的全部目的是使数据库不可见。为了让您可以本机地使用对象,可以在数据库中神奇地使用数据库来使对象持久化。 NHibernate将允许您本地使用数据库,但这不是为NHibernate构建的场景类型。

答案 2 :(得分: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,因为AFAIK NHibernate不能被告知属性是只读的(因此对DB来说是“只写”);在NHibernate的世界里,如果你向DB写一些东西,你为什么不想要它呢?

我最近使用这种模式允许用户更改“游览”的基本类型,这实际上是一组规则,用于管理实际“游览”的调度(对客户端的单个数字“访问”)现场设备,以确保一切正常工作)。虽然它们都是“巡视时间表”并且需要可以在列表/队列等中收集,但是不同类型的时间表需要非常不同的数据和非常不同的处理,需要与OP具有类似的数据结构。因此,我完全理解OP希望以一种截然不同的方式处理TierOneCustomer,同时最大限度地减少数据层的影响,所以,现在就去。

答案 3 :(得分:1)

如果您正在脱机(例如在数据库升级脚本中),只需使用SQL并确保自己的一致性。

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

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