Как справиться с NHibernate родителями \ дочерними отношениями с childs в ISet и сгенерированными идентификаторами? - PullRequest
1 голос
/ 18 февраля 2010

Войдя в мир NHibernate менее года назад, я все еще развиваю свою "личная" лучшая практика и архитектурные решения в этой области ... теперь я сталкиваюсь с довольно простой вопрос, по которому я хотел бы, чтобы кто-то больше опыта.

Сценарий следующий: прямые родительские / дочерние отношения, хранящиеся в ISet на родительской стороне.

Классы:

public class IDPersisted<IdentifierType>
{
    private IdentifierType _id;

    public IDPersisted()
    {
        _id = default(IdentifierType);
    }

    public IDPersisted(IdentifierType id)
    {
        _id = id;
    }
}

public class SportCenter : IDPersisted<Guid>
{
    ...

    private ISet<Field> _fields = new HashedSet<Field>();

    public bool AddField(Field field)
    {
         field.SportCenter = this;
         return _fields.Add(field);
    }

    public bool RemoveField(Field field)
    {
         field.SportCenter = null;
         return _fields.Remove(field);
    }

    ...
}

public class Field : IDPersisted<Guid>
{
    public Field(String name)
    {
        Name = name;
    }

    public String Name { get; set; }

    public SportCenter SportCenter { get; set; }

    ...
}

Как видите, у меня есть базовый класс, реализующий общий оператор Identifier, операторы Equals / GetHashCode и == /! = Полагаются на поле ID, которое считается неизменным в течение жизни экземпляра класса. Классы SportCenter (родительский) и Filed (дочерний) сохраняются с идентификатором Guid. SportCenter содержит коллекцию (фактически, Набор) экземпляров Field, управляющих двунаправленной связью в методах Add / RemoveField.

Отображения:

<class name="SportCenter" table="SportCenters" lazy="false">
   <id name="ID" column="SportCenterID" >
      <generator class="guid.comb" />
   </id>
    ...
   <set name="Fields" table="Fields" inverse="true" lazy ="true" cascade="all-delete-orphan" access="field.camelcase-underscore">
      <key column="SportCenterID" />
      <one-to-many class="Field" />
   </set>
</class>

<class name="Field" table="Fields" lazy="false">
   <id name="ID" column="FieldID" type="Guid">
      <generator class="guid.comb" />
   </id>
   <property name="Name" column="Name" type="String" not-null="true" />
    ...
   <many-to-one name="SportCenter" column="SportCenterID" class ="SportCenter" not-null="true" lazy="proxy" />
</class>

Я принял все основные советы, которые вы получаете для отображения такого рода отношений: inverse = "true", Set не доступен напрямую (access = "field"), каскадные и ненулевые ...

В моем BLL у меня есть метод для создания нового поля внутри определенного SportCenter:

public void CreateField(FieldDTO fieldDTO, Guid sportcenterID)
{
   // Retrieve the SportCenter
   SportCenter sportcenter = SportCentersDAO.GetByID(sportcenterID);

   // Prepare the new Field object
   Field field = new Field(fieldDTO.Name);
   ...

   // Add the new field to the SportCenter
   sportcenter.AddField(field);

   // Save the SportCenter
   SportCentersDAO.Save(sportcenter);
}

Что происходит, когда я создаю новый экземпляр Field, его ID устанавливается на Guid.Empty (то есть все нули), тогда я вызываю AddField, и поле добавляется в Set с этим ID ... тогда, только в SportCentersDAO.Save вызов реального Guid назначен field.ID.

Это нарушает одно из правил, которые вы можете найти в каждой спецификации / документации / книге о NHibernate, а именно: Никогда не меняйте идентификатор экземпляра, пока он хранится в ISet (поскольку идентификатор - это свойство, с помощью которого экземпляры сравниваются с помощью Equals (), как я написал выше).

Предоставление в поле значения Guid для идентификатора перед добавлением его в набор приведет меня к злым «назначенным» идентификаторам и, если вам придется ответить, не полагаться на идентификатор для Equals/GetHashCode и найти естественную модель связанный ключ ... ну, я не верю, что когда-либо обнаруживал в этих свойствах, которые обеспечивают неизменность / уникальность, которых заслуживает "ключ".

Я что-то не так делаю !? Что ты думаешь по этому поводу?

Заранее спасибо, Питер.

1 Ответ

2 голосов
/ 18 февраля 2010

У меня почти такой же базовый класс для моих сущностей, как и у вас.

Базовый класс имеет переопределенные методы Equals / GetHashcode и т. Д. В моей реализации метода Equals вБазовый класс, я проверяю, является ли сущность временным (еще не постоянным).Сущность является переходной, когда Id, который был ей назначен, по-прежнему является значением по умолчанию.

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

В моей реализации GetHashCode я делаю это:

  • У меня есть личная переменная-член oldHashcode в моей сущности, которая имеет тип NULL.
  • когда oldHashCode не равен нулю, я возвращаю oldHashCode.
  • , в противном случае, когда сущность является переходной, я вызываю base.GetHashCode() и сохраняю возвращенное значение в oldHashCode.Я возвращаю это значение.
  • , в противном случае я генерирую хэш-код на основе идентификатора.Id.GetHashCode()

    private int? _oldHashCode;
    
        public override int GetHashCode()
        {
            if( _oldHashCode.HasValue )
            {
                return _oldHashCode.Value;
            }
    
            if( IsTransient )
            {
                _oldHashCode = base.GetHashCode ();
    
                return _oldHashCode.Value;
            }
    
            return Id.GetHashCode ();           
        }
    

Используя такую ​​стратегию, я никогда не сталкивался с какими-либо странными проблемами до сих пор ...

...