views:

129

answers:

1

Having entered the world of NHibernate less than one year ago, I'm still developing my "personal" best practice and architectural solutions in this area... now I'm facing a pretty simple issue on which I'd like to have opinions or suggestions by somebody with more expertise.

The scenario is the following: A straight parent/child relationship stored in an ISet on parent side.

Classes:

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; }

    ...
}

As you can see, I have a base class implementing a generic Identifier, Equals/GetHashCode and ==/!= operators all rely on the ID field which is considered to be immutable during the life of a class instance. Both SportCenter (parent) and Filed (child) classes are persisted with a Guid identifier. The SportCenter holds a collection (actually, a Set) of Field instances managing the bidirectional relationship in the Add/RemoveField methods.

Mappings:

<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>

I've adopted all the basic advices you get for mapping this kind of relationship: inverse = "true", Set is not directly accessible (access="field"), cascading and not-nulls...

In my BLL I have a method to create a new Field inside a specific 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);
}

What happens is that when I create the new Field instance its ID is set to Guid.Empty (that is all zeros) then I call AddField and the field is added to the Set with that ID...then, only at the SportCentersDAO.Save call a real Guid is assigned to field.ID.

This breaks one of the rules you can find in every spec/doc/book about NHibernate that is: never change the ID of an instance while it is stored in an ISet (since the ID is the property by which instances are compared by Equals() as I wrote uphere).

Providing the field with a Guid value for ID before adding it to the Set will take me to the evil "assigned" IDs and, if you were to answer to not rely on ID for Equals/GetHashCode and to find a natural model-related key...well, I don't believe I've ever found in these properties which provide the immutabilty/uniqueness a "key" deserves.

Am I doing something wrong!? What's your opinion on this?

Thank you in advance, Peter.

+1  A: 

I have pretty much te same kind of base-class for my entities like you have.

The base class has overriden Equals / GetHashcode methods, etc... In my implementation of the Equals method in the base class, I check whether the entity is transient (not yet persistent). An entity is transient, when the Id that has been assigned to it, is still the default value.

When this is the case, I do not check the equality based on the Id.
If both entities that have to be compared, are transient, I use the ReferenceEquals method in order to determine equality.

In my implementation of GetHashCode, I do this:

  • I have a private member variable oldHashcode in my entity, which is of a nullable type.
  • when oldHashCode is not null, I return oldHashCode.
  • else, when the entity is transient, I call base.GetHashCode() and store the returned value in oldHashCode. I return this value.
  • else, I generate the Hashcode based on the Id. 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 ();           
    }
    

Using this kind of 'strategy', I've never ran into any weird issues 'till now...

Frederik Gheysels
I like the idea of checking for a default value in the Equals implementation to detect not-yet-persisted entities. But I'm not getting what you do in GetHashCode and, to be honest, seems more like an hack that a "correct" straightforward implementation. Whould you post some code please?! Thanks a lot for your answer.
Peter
I do not think it is a hack, since it complies to the rule that a hashcode should not change during the lifetime of an object.
Frederik Gheysels