Равно реализации сущностей NHibernate, unproxy вопрос

в Поваренной книге NHibernate 3.0 есть пример реализации для базового типа сущности. Равно реализуется так:

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, конкретная книга классов, которая наследуется от Product, и динамический прокси-класс ProductProxy, используемый NHibernate для ленивой загрузки. Если ProductProxy, представляющий книгу и конкретную книгу, имеют одинаковые идентификаторы, их следует рассматривать как равные. Однако я действительно не понимаю, почему вызов GetType() на экземпляре ProductProxy должен возвращать продукт в этом случае и как это помогает. Есть идеи?

3 ответов


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

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

когда NHibernate возвращает прокси для ленивой загрузки, он возвращает экземпляр прокси, унаследованный от фактического типа. Есть несколько членов, к которым мы можем получить доступ без принудительной загрузки из базы данных. Среди них свойство или поле Id прокси,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)."


мы используем NH 2, и этот пример не работает для нас. (Это не unproxy тип и левый тип прокси, см. ниже). Он сказал, что 2 объекта с одинаковым идентификатором не равны, когда один из них является прокси(Корганизации), а другой-нет(Дорганизации). Когда у нас была иерархия:

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 попытался снова установить поле Корганизации (с тем же идентификатором), GetUnproxiedType работал неправильно, новые и старые значения были признаны неравными, и было создано исключение...

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

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

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

в отладчике: otherType == COrganizationProxy -ошибка GetUnproxiedType... тип == DOrganization

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

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

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

или просто "cOrganization как COrganization" вместо "(COrganization)(cOrganization.GetConcrete ())"?

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

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

С течением (v5.x) фабрики прокси NHibernate (статические или динамические, статические, доступные с v5.1), Эта картина фактически сломлена. Встроенные фабрики прокси v5 не перехватывают частные методы.

и я думаю, что это уже было в случае V4.

для этого шаблона для работы с текущими встроенными фабриками прокси,GetUnproxiedType должно быть virtual (не private кстати, да protected).

в противном случае, используйте NHibernateUtil.GetClass, который предназначен для этого и не стоит полагаться на ломкие уловки. Его документация предупреждает, что он инициализирует прокси-сервер побочным эффектом, но в любом случае GetUnproxiedType уловка должна сделать то же самое для работы.
Конечно, используя NHibernateUtil.GetClass означает наличие прямой зависимости от NHibernate в базовом классе модели домена. Но в зависимости от трюка реализации, специфичного для внешней (с точки зрения домена) реализации библиотеки, на мой взгляд, не лучше.

кроме того, некоторые изменения могут вызвать GetUnproxiedType трюк, чтобы быть еще более сломанным в будущем, как некоторые идеи для уменьшения количества случаев, вызывающих инициализацию прокси-сервера, когда этого можно было избежать. (См.здесь пример.)

если вы действительно хотите GetUnproxiedType метод не зависит от прямой ссылки NHibernate, я думаю, что единственное теоретически "безопасное" решение-это иметь его абстрактным и переопределенным в каждом конкретном классе сущностей для получения typeof(YourEntityClass). Но на практике это было бы громоздко и подвержено ошибкам (плохо copy-paste для создания нового объекта, забывая изменить этот метод...), в то время как абстрактная часть не поможет в случае, если некоторые конкретные классы сущностей дополнительно специализируются через наследование.

еще один трюк, может быть, от типа полученных GetType, чтобы проверить, к какой сборке он принадлежит(тип прокси не будет принадлежать одной из ваших сборок), для поиска первого типа в иерархии, принадлежащей вашей сборке (сборкам) модели домена.
Обратите внимание, что если прокси-сервер прокси базового класса, а не конкретного класса, и ваш вспомогательный метод установлен как private, он даст тип базового класса без инициализации прокси. С точки зрения производительности, это лучше. В то время как виртуальный GetUnproxiedType просто возвращение GetType вернет конкретный тип класса с текущими фабриками прокси, но он также инициализирует прокси.