Равен реализации NHibernate Entities, вопрос с прокси - PullRequest
12 голосов
/ 16 апреля 2011

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

Ответы [ 3 ]

7 голосов
/ 20 апреля 2011

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

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

Когда 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). "

2 голосов
/ 10 июня 2011

Мы используем NH 2, и этот пример у нас не работает.(Не удалось распаковать тип и левый тип прокси, см. Ниже).В нем говорится, что 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 попытался снова установить поле COrganization (с тем же идентификатором), GetUnproxiedType работал неправильно, новые и старые значения были распознаны как не равные, и было выдано исключение ...

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

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

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

В отладчике: otherType == COrganizationProxy - Сбой GetUnproxiedType ... thisType == DOrganization

COrganizationProxy и DOrganizationунаследовать организацию.Так что они не являются IsAssignableFrom друг для друга ...

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

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

Или из-за простого "Организация как организация" вместо "(Организация) (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);
    }
}
1 голос
/ 20 сентября 2018

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

И я думаю, что это уже относится к v4.

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...