Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equals implementation of NHibernate Entities, unproxy question

In NHibernate 3.0 Cookbook, there is a sample implementation for a base Entity type. The equals is implemented like this:

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(this) && Equals(Id, other.Id))
    {
      var otherType = other.GetUnproxiedType();
      var thisType = GetUnproxiedType();
      return thisType.IsAssignableFrom(otherType) ||
         otherType.IsAssignableFrom(thisType);
    }
    return false;
  }    
}

The reason for the GetUnproxiedType() method is this: There is an abstract base class Product, a concrete class Book which inherits from Product and a dynamic proxy class ProductProxy used by NHibernate for lazy loading. If a ProductProxy representing a Book and a concrete Book have the same Ids, they should be treated as equal. However I don't really see why calling GetType() on a ProductProxy instance should return Product in this case, and how it helps. Any ideas?

like image 553
Can Gencer Avatar asked Apr 16 '11 12:04

Can Gencer


2 Answers

I actually went ahead and wrote to the author of the book about this code. It turns out this is due to how the proxy wrapping works. Here is his response:

"If you don't understand how the proxy frameworks work, the idea can seem magical.

When NHibernate returns a proxy for the purposes of lazy loading, it returns a proxy instance inherited from the actual type. There are a few members we can access without forcing a load from the database. Among these are proxy's Id property or field, GetType(), and in some circumstances Equals() and GetHashCode(). Accessing any other member will force a load from the database.

When that happens, the proxy creates an internal instance. So, for example, a lazy loaded instance of Customer (CustomerProxy102987098721340978), when loaded, will internally create a new Customer instance with all of the data from the database. The proxy then does something like this:

public overrides string Name 
{ 
    get { 
       return _loadedInstance.Name; 
    } 
    set { _loadedInstance.Name = value; } 
}

Incidentally, it's this overriding that requires everything to be virtual on entities that allow lazy loaded.

So, all calls to the Name property on the proxy are relayed to the internal Customer instance that has the actual data.

GetUnproxiedType() takes advantage of this. A simple call to GetType() on the proxy will return typeof(CustomerProxy02139487509812340). A call to GetUnproxiedType() will be relayed to the internal customer instance, and the internal customer instance will return typeof(Customer)."

like image 72
Can Gencer Avatar answered Oct 22 '22 22:10

Can Gencer


With current (v5.x) NHibernate proxy factories (static or dynamic, static being available since v5.1), this pattern is actually broken. The v5 built-in proxy factories do not intercept private methods.

And I think that was already the case for v4 ones.

For this pattern to work with current built-in proxy factories, GetUnproxiedType should be virtual (so not private by the way, but protected).

Otherwise, use NHibernateUtil.GetClass, which is meant for this and does not rely on brittle tricks. Its documentation warns it will initialize the proxy by side effect, but anyway the GetUnproxiedType trick must do the same for working.
Of course using NHibernateUtil.GetClass means having a direct dependency to NHibernate in a domain model base class. But depending on an implementation trick specific to an external (from the domain viewpoint) library implementation is no better in my opinion.

Moreover, some changes may cause the GetUnproxiedType trick to be even more broken in the future, like some ideas for reducing the number of cases causing a proxy to get initialized when it could be avoided. (See here by example.)

If you really want a GetUnproxiedType method not depending on a direct NHibernate reference, I think the only theoretically "safe" solution is to have it abstract and overridden in each concrete entity class for yielding typeof(YourEntityClass). But in practice, it would be cumbersome and error-prone (bad copy-paste for creating a new entity, forgetting to change that method...), while the abstract part won't help in case some concrete entity classes are further specialized through inheritance.

Another trick could be, from the type obtained by GetType, to check to which assembly it belongs (the proxy type will not belong to one of your assemblies), for searching the first type in the hierarchy belonging to your domain model assembly(ies).
Note that if the proxy is a proxy of a base class, not of a concrete class, and your helper method is set as private, it will yield the base class type, without initializing the proxy. Performance-wise, this is better. While a virtual GetUnproxiedType simply returning GetType would return the concrete class type with current proxy factories, but it would also initialize the proxy.

like image 29
Frédéric Avatar answered Oct 22 '22 23:10

Frédéric