Уравнивает реализацию 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);
}
}