Setting up a base entity class

In this recipe, we'll show how to set up a base class for your entities. The purpose of this class is to provide base implementations of potentially tricky Equals and GetHashCode methods.

How to do it…

  1. We create a base class, where the type of Id is specified using a generic argument, as shown:
    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 use this class, our entity classes can simply derive from Entity<TId> to inherit the required functionality:
    public class Employee : Entity<Guid>
    {
      public virtual string FirstName {get;set;}
      public virtual string LastName {get;set;}
    }

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. In other words, 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 would 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) 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, the default value, whatever it may be, for TId. For Guid instance, 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 do not match, we know that the two entities are not equal and we return false.

Finally, we have one last check to do. We know that both entities are persistent and they have the same Id; however, this doesn't 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 that the two are equal.

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

Since we have 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 can 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
..................Content has been hidden....................

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