NHibernate - Изменение подтипов - PullRequest
6 голосов
/ 26 января 2009

Как вы можете изменить подтип строки в NHibernate? Например, если у меня есть сущность Customer и подкласс TierOneCustomer, у меня есть случай, когда мне нужно изменить Customer на TierOneCustomer, но TierOneCustomer должен иметь тот же Id (PK), что и исходная сущность Customer.

Отображение выглядит примерно так:

<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 нужно просто обновить sql для дискриминатора (CustomerType) и установить соответствующие столбцы, соответствующие типу. Я не могу найти решение в NHibernate, поэтому буду признателен за любые указатели.

Я также думаю, является ли модель правильной, учитывая этот вариант использования, но прежде чем идти по этому пути, я хочу убедиться, что выполнение, как описано выше, действительно возможно в первую очередь. Если нет, я почти наверняка подумаю о смене модели.

Ответы [ 4 ]

10 голосов
/ 26 января 2009

Краткий ответ - да, вы можете изменить значение дискриминатора для конкретной строки (строк), используя собственный SQL .

Однако я не думаю, что NHibernate предназначен для такой работы, поскольку дискриминатор, как правило, «невидим» для уровня Java, где его значение должно устанавливаться изначально в соответствии с классом сохраняемого объекта и никогда измененное.

Я рекомендую рассмотреть более чистый подход. С точки зрения объектной модели, вы пытаетесь преобразовать объект суперкласса в один из его типов подкласса, не изменяя идентичность его сохраняемого экземпляра, и именно в этом заключается конфликт (преобразованный объект не должен быть тоже самое). Два альтернативных подхода:

  • Создайте новый экземпляр TierOneCustomer на основе информации в исходном объекте Customer, затем удалите исходный объект. Если вы использовали Первичный ключ Клиента для поиска, вам нужно будет принять к сведению новый PK.

или

  • Измените свой подход, чтобы тип объекта (дискриминатор) не нуждался в изменении. Вместо того, чтобы полагаться на подкласс, чтобы отличать TierOneCustomer от Customer, вы можете использовать свойство, которое вы можете свободно изменять в любое время, то есть Customer.Tier = 1.

Вот некоторые связанные обсуждения на форумах Hibernate, которые могут представлять интерес:

  1. Можем ли мы обновить столбец дискриминатора в Hibernate
  2. Проблема таблицы в классе: дискриминатор и свойство
  3. Преобразование сохраненного экземпляра в подкласс
6 голосов
/ 26 января 2009

Вы делаете что-то не так.

То, что вы пытаетесь сделать, это изменить тип объекта. Вы не можете сделать это в .NET или в Java. Это просто не имеет смысла. Объект имеет ровно один конкретный тип, и его конкретный тип не может быть изменен с момента создания объекта до момента уничтожения объекта (несмотря на черную магию). Чтобы выполнить то, что вы пытаетесь сделать, но с иерархией классов, которую вы выложили, вам придется уничтожить объект клиента, который вы хотите превратить в объект клиента первого уровня, создать новый объект клиента первого уровня, и скопируйте все соответствующие свойства из объекта клиента в объект клиента первого уровня. Вот как вы делаете это с объектами, в объектно-ориентированных языках, с вашей иерархией классов.

Очевидно, что иерархия классов у вас не работает для вас. Вы не уничтожаете клиентов в реальной жизни, когда они становятся клиентами первого уровня! Так что не делайте этого с объектами тоже. Вместо этого придумайте иерархию классов, которая имеет смысл, учитывая сценарии, которые вам необходимо реализовать. Ваши сценарии использования включают в себя:

  • Клиент, который ранее не имел статуса первого уровня, теперь становится статусом первого уровня.

Это означает, что вам нужна иерархия классов, которая может точно отразить этот сценарий. Как подсказка, вы должны отдавать предпочтение композиции, а не наследованию. Это означает, что может быть лучше иметь свойство с именем IsTierOne или свойство с именем DiscountStrategy и т. Д., В зависимости от того, что работает лучше.

Целью NHibernate (и Hibernate для Java) является сделать базу данных невидимой. Чтобы позволить вам работать с объектами изначально, с волшебной базой данных, которая делает ваши объекты постоянными. NHibernate позволит вам работать с базой данных изначально, но это не тот тип сценария, для которого построен NHibernate.

2 голосов
/ 25 августа 2012

Это действительно поздно, но может быть полезно следующему человеку, желающему сделать что-то подобное:

В то время как другие ответы верны, вы не должны изменять дискриминатор в большинстве случаев, вы можете делать это исключительно в рамках 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 сохраняется (как если бы запись была клонирована или не (связанный с пользовательским интерфейсом к новому типу), значение дискриминатора обновляется в существующей записи с этим идентификатором в качестве свойства объекта, так что будущие извлечения этого типа будут как новый объект. Установщик требуется для свойств, потому что AFAIK NHibernate нельзя сказать, что свойство доступно только для чтения (и, следовательно, «только для записи» в БД); в мире NHibernate, если ты пишешь что-то в БД, почему ты не хочешь вернуть это обратно?

Я недавно использовал этот шаблон, чтобы позволить пользователям изменять базовый тип «тура», который на самом деле представляет собой набор правил, управляющих планированием фактического «тура» (одно цифровое «посещение» клиента -сайтовое оборудование, чтобы все работало нормально). Хотя все они являются «расписаниями туров» и должны собираться в списках / очередях и т. Д. Как таковые, для разных типов расписаний требуются очень разные данные и очень разная обработка, требующая аналогичной структуры данных, как у OP. Поэтому я полностью понимаю желание ОП относиться к TierOneCustomer существенно другим способом, минимизируя эффект на уровне данных, так что здесь все.

1 голос
/ 26 января 2009

Если вы делаете это в автономном режиме (например, в сценарии обновления БД), просто используйте SQL и убедитесь в его согласованности.

Если это то, что, как вы планируете, произойдет во время работы приложения, я думаю, что ваши требования неверны, так же, как неправильное сохранение одного и того же адреса указателя для другого объекта.

Если вы сохраняете идентификатор и используете его для повторного доступа к клиенту (например, в URL), рассмотрите возможность создания нового поля, содержащего маркер для этого, который станет бизнес-ключом. Поскольку это не идентификатор, легко создать новый экземпляр сущности и скопировать токен (вам, вероятно, потребуется удалить токен из старого).

...