Уравнивает реализацию NHibernate Entities, непрофильный вопрос

В NHIBernate 3.0 Cookbook существует примерная реализация для базового типа Entity. Уравнения реализуются следующим образом:

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

Причина метода GetUnproxiedType() заключается в следующем: существует абстрактный базовый класс Product, конкретный класс Book, который наследует от Product и динамический прокси-класс ProductProxy, используемый NHibernate для ленивой загрузки. Если ProductProxy, представляющий книгу и конкретную книгу, имеет одинаковые идентификаторы, их следует рассматривать как равные. Однако я не понимаю, почему вызов GetType() в экземпляре ProductProxy должен возвращать Product в этом случае и как это помогает. Любые идеи?

Ответ 1

Я действительно пошел вперед и написал автору книги об этом коде. Оказывается, это связано с тем, как работает прокси-упаковка. Вот его ответ:

"Если вы не понимаете, как работают прокси-структуры, идея может показаться волшебной.

Когда NHibernate возвращает прокси для целей ленивой загрузки, он возвращает экземпляр прокси, унаследованный от фактического типа. Есть несколько членов, к которым мы можем получить доступ, не вызывая нагрузки из базы данных. Среди них - свойство или поле идентификатора прокси, GetType(), а в некоторых случаях Equals() и GetHashCode(). Доступ к любому другому члену приведет к загрузке из базы данных.

Когда это произойдет, прокси создает внутренний экземпляр. Так, например, ленивый загруженный экземпляр Customer (CustomerProxy102987098721340978) при загрузке будет внутренне создавать новый экземпляр Customer со всеми данными из базы данных. Затем прокси делает следующее:

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

Кстати, это переопределение, которое требует, чтобы все было виртуальным для объектов, которые позволяют лениво загружаться.

Таким образом, все вызовы свойства Name в прокси передаются во внутренний экземпляр Customer, который имеет фактические данные.

GetUnproxiedType() использует это. Простой вызов GetType() в прокси-сервере вернет typeof(CustomerProxy02139487509812340). Вызов GetUnproxiedType() будет передан во внутренний экземпляр клиента, а внутренний экземпляр клиента вернет typeof(Customer). "

Ответ 2

Мы используем NH 2, и этот пример не сработал для нас. (Это НЕИСПРАВНО, чтобы отключить тип и левый тип прокси, см. Ниже). Он сказал, что 2 объекта с одинаковым идентификатором не равны, когда один из них является прокси (из COrganization), а другой не является (DOrganization). Когда у нас была иерархия:

class Organization
class AOrganization : Organization
class COrganization : Organization
{
  public virtual COrganization GetConcrete()
  {
    return null;
  }
}

class DOrganization : COrganization
{
  public virtual COrganization GetConcrete()
  {
    return this;
  }
}

AOrganization aOrganization;
COrganization cOrganization;
contract = new CContract(aOrganization, cOrganization as COrganization); //(COrganization)(cOrganization.GetConcrete()),

Итак, у CContract есть поле типа COrganization. С помощью сеттера

public class Contract: Entity <short>
{
    public virtual COrganization COrganization
    {
        get { return cOrganization; }
        protected internal set
        {
            if (cOrganization != null && value != cOrganization) // != calls ==, which calls Equals, which calls GetUnproxiedType()
                    throw new Exception("Changing organization is not allowed.");
            }
            cOrganization = value;
        }
    }
    private COrganization cOrganization;
}

Мы построили новый контракт, его конструктор задает поле COrganization, указывающее на некоторую организацию. Затем мы вызвали UnitOfWork.Commit, NH попытался снова установить поле COrganization (с тем же идентификатором), GetUnproxiedType работал некорректно, новые и старые значения были признаны не равными, а исключение было выбрано...

Вот место, где появилась ошибка:

            var otherType = other.GetUnproxiedType();
            var thisType = GetUnproxiedType();

            return thisType.IsAssignableFrom(otherType) ||
            otherType.IsAssignableFrom(thisType);

В отладчике: otherType == COrganizationProxy - GetUnproxiedType не удалось...  thisType == DOrganization

COrganizationProxy и DOrganization оба наследуют COrganization. Поэтому они не являются IsAssignableFrom друг для друга...

Почему этот пример работает для вас?

Возможно, потому что у нас есть NH 2.0 или 2.1?

Или из-за простой "cOrganization as COrganization" вместо "(COrganization) (cOrganization.GetConcrete())" ?

Или потому, что у нас есть реализация ==,!= и Equals не только в Entity, но и в организации?

public abstract class Organization : Entity<int>
{
    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    public static bool operator ==(Organization object1, Organization object2)
    {
        return AreEqual(object1, object2);
    }

    public static bool operator !=(Organization object1, Organization object2)
    {
        return AreNotEqual(object1, object2);
    }
}

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

    /// This method added by me
    /// For == overloading
    protected static bool AreEqual<TEntity>(TEntity entity1, TEntity entity2)
    {
        if ((object)entity1 == null)
        {
            return ((object)entity2 == null);
        }
        else
        {
            return entity1.Equals(entity2);
        }
    }

    /// This method added by me
    /// For != overloading
    protected static bool AreNotEqual<TEntity>(TEntity entity1, TEntity entity2)
    {
        return !AreEqual(entity1, entity2);
    }
}