代码之家  ›  专栏  ›  技术社区  ›  Andy Whitfield

NHibernate-改变子类型

  •  7
  • Andy Whitfield  · 技术社区  · 17 年前

    你如何改变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 回复  |  直到 17 年前
        1
  •  11
  •   David Crow    17 年前

    简短的回答是肯定的,您可以使用以下命令更改特定行的鉴别器值 native SQL .

    然而,我不认为NHibernate是这样工作的,因为鉴别器通常对Java层“不可见”,在Java层,它的值应该最初根据持久对象的类设置,永远不会改变。

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

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

    或

    • 改变你的方法,这样对象类型(鉴别器)就不需要改变。您可以使用一个可以随时自由修改的属性,即Customer,而不是依赖子类来区分TierOneCustomer和Customer。等级=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
        2
  •  6
  •   yfeldblum    17 年前

    你做错了什么。

    您要做的是更改对象的类型。你不能在里面这样做。NET或Java。这根本说不通。对象只有一种具体类型,从创建对象到销毁对象(尽管有黑魔法),其具体类型不能更改。为了完成您正在尝试做的事情,但使用您设置的类层次结构,您必须销毁要转换为第一层客户对象的客户对象,创建一个新的第一层客户目标,并将所有相关属性从客户对象复制到第一层客户对象。在面向对象的语言中,通过类层次结构,你就是这样处理对象的。

    显然,你的类层次结构不适合你。当客户成为一级客户时,你不会在现实生活中摧毁他们!所以也不要对物体这样做。相反,考虑到你需要实现的场景,想出一个有意义的类层次结构。您的使用场景包括:

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

    这意味着你需要一个能够准确捕捉这种情况的类层次结构。作为提示,你应该更喜欢组合而不是继承。这意味着,最好有一个名为的属性 IsTierOne ,或指定的财产 DiscountStrategy 等等,这取决于什么最有效。

    NHibernate(和Hibernate for Java)的全部目的是使数据库不可见。允许您以本机方式处理对象,在幕后神奇地使用数据库使您的对象持久化。NHibernate将允许您在本地使用数据库,但这不是NHibernates构建的场景类型。

        3
  •  2
  •   KeithS    12 年前

    这真的很晚了,但可能对下一个想做类似事情的人有用:

    虽然其他答案都是正确的,但你 不应该 在大多数情况下,改变鉴别器,你 能 纯粹在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希望以一种截然不同的方式对待TierOne客户,同时尽量减少数据层的影响,所以,开始吧。

        4
  •  1
  •   orip    17 年前

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

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

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