Setting up a base entity class

In this recipe, I'll show you how to set up a base class to use for your entities.

Getting ready

Complete the previous three recipes.

How to do it...

  1. In Entity.cs, use the following code for the Entity class:
    public abstract class Entity<TId>
    {
    
      public virtual TId Id { get; protected set; }
    
      public override bool Equals(object obj)
      {
        return Equals(obj as Entity<TId>);
      }
    
      private static bool IsTransient(Entity<TId> obj)
      {
        return obj != null &&
               Equals(obj.Id, default(TId));
      }
    
      private Type GetUnproxiedType()
      {
        return GetType();
      }
    
      public virtual bool Equals(Entity<TId> other)
      {
        if (other == null)
          return false;
    
        if (ReferenceEquals(this, other))
          return true;
    
        if (!IsTransient(this) &&
            !IsTransient(other) &&
            Equals(Id, other.Id))
        {
          var otherType = other.GetUnproxiedType();
          var thisType = GetUnproxiedType();
          return thisType.IsAssignableFrom(otherType) ||
                 otherType.IsAssignableFrom(thisType);
        }
    
        return false;
      }
    
      public override int GetHashCode()
      {
        if (Equals(Id, default(TId)))
          return base.GetHashCode();
        return Id.GetHashCode();
      }
    
    }
  2. To the same file, add an additional Entity class as shown in the following code:
    public abstract class Entity : Entity<Guid>
    {
    }

How it works...

NHibernate relies on the Equals method to determine equality. The default behavior defined in System.Object uses reference equality for reference types, including classes. That is, x.Equals(y) is only true when x and y point to the same object instance. This default works well in most cases.

To support lazy loading, NHibernate uses proxy objects. As we learned in the previous recipe, these proxy objects are subclasses of the real entity class, with every member overridden to enable lazy loading.

This combination of proxy objects and the default Equals behavior can lead to subtle and unexpected bugs in your application. An application should not be aware of proxy objects, and therefore would expect that a proxy and a real instance representing the same entity would be equal. A Product instance with an ID of 8 should be equal to a different Product instance or Product proxy with an ID of 8. To handle this, we must override the default Equals behavior.

On our Entity base class, we override the Equals method to determine equality based on POID. In Equals(Object obj), we simply call Equals(Entity<TId> other), attempting to cast the object to Entity. If it can't be cast, null is passed instead.

If other is null, the objects are not equal. This serves two purposes. First, x.Equals(null) should always return false. Second, someEntity.Equals(notAnEntity) should also return false. Next, we compare references. Obviously, if two variables reference the same instance, they are equal. If ReferenceEquals(this, other) returns true, we return true.

Next, we compare the Ids to the default value to determine if the entities are transient. A transient object is an object that has not been persisted to the database. default(TId) returns whatever the default may be for TId. For Guids, the default is Guid.Empty. For strings and all other reference types, it's null. For numeric types, it's zero. If the Id property equals the default value, the entity is transient. If one or both entities are transient, we give up and return false.

If both entities are persisted, they both have POIDs. We can compare these POIDs to determine equality. If the POIDs don't match, we know for certain that the two entities are not equal. We return false.

Finally, we have one last check. We know that both entities are persistent, and they have the same Id. This doesn't quite prove that they're equal. It's perfectly legal for an ActorRole entity to have the same POID as a Product entity. Our last check is to compare the types. If one type is assignable to the other type, then we know for certain that the two are equal.

Suppose other is a proxy of Product representing a book entity, and this is an actual Book instance representing the same entity. this.Equals(other) should return true because they both represent the same entity. Unfortunately, other.GetType() will return the type ProductProxy12398712938 instead of the type Product. As typeof(ProductProxy12398712938).IsAssignableFrom(typeof(Book)) returns false, our Equals would fail on this case. However, we can use other.GetUnproxiedType() to reach down through the proxy layer and return the entity type. Because typeof(Product).IsAssignableFrom(typeof(Book)) returns true, our Equals implementation works.

Because we've overridden Equals, we also need to override GetHashCode to satisfy the requirements of the .NET Framework. Specifically, if x.Equals(y), then x.GetHashCode() and y.GetHashCode() should return the same value. The inverse is not necessarily true, however; x and y may share a hash code even when they're not equal. In our Entity base class, we simply use the hash code of Id, as this is the basis of our equality check.

There's more...

For more information on Equals and GetHashCode, refer to the MSDN documentation for these methods at http://msdn.microsoft.com/en-us/library/system.object.aspx.

See also

  • Mapping a class with XML
  • Creating class hierarchy mappings
  • Mapping a one-to-many relationship
  • Bidirectional one-to-many class relationships
  • Handling versioning and concurrency
  • Creating mappings fluently
  • Mapping with ConfORM
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset