-
Notifications
You must be signed in to change notification settings - Fork 934
Description
Hello,
this is a follow up to issue #2043.
The nhiberante documentation states in 21.1.3. Single-ended association proxies that the id of proxies can be accessed without initialization
Certain operations do not require proxy initialization
- Equals(), if the persistent class does not override Equals()
- GetHashCode(), if the persistent class does not override GetHashCode()
- The identifier getter method
Let me explain one of our core concepts of our schema. We are heavily using union-subclassing inheritance with abstract base classes. Some of our models have a proxy definition some not. We are also mixing hbm mappings with fluent automapping.
Please don't blame me for our database schema (it's not mine), or how my (former) co-workers were/are misusing it :)
So let's say there are the following tables:
public class EntityClassProxy : IEntity
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
class SubEntityInterfaceProxy : EntityClassProxy, ISubEntityProxy
{
public virtual string AnotherName { get; set; }
}
class AnotherSubEntityInterfaceProxy : EntityClassProxy, IAnotherSubEntityProxy
{
public virtual string AnotherName { get; set; }
}
as well the following table with a lookup:
public class EntityWithSuperClassInterfaceLookup
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IEntity EntityLookup { get; set; }
}
with the following mapping: The base class proxy definition is the class itself. The subclasses proxy definitions point to interfaces.
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<EntityClassProxy>(rc =>
{
// calling the id getter of the proxy triggers a database query
rc.Proxy(typeof(EntityClassProxy));
// calling the id getter of the proxy doesn't trigger a database query
//rc.Proxy(typeof(IEntity));
rc.Id(x => x.Id);
rc.Property(x => x.Name);
});
mapper.UnionSubclass<SubEntityInterfaceProxy>(rc =>
{
rc.Proxy(typeof(ISubEntityProxy));
rc.Property(x => x.AnotherName);
});
mapper.UnionSubclass<AnotherSubEntityInterfaceProxy>(rc =>
{
rc.Proxy(typeof(IAnotherSubEntityProxy));
rc.Property(x => x.AnotherName);
});
mapper.Class<EntityWithSuperClassInterfaceLookup>(rc =>
{
rc.Id(x => x.Id);
rc.Property(x => x.Name);
rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityClassProxy)));
});
return mapper.CompileMappingForAllExplicitlyAddedEntities();
}
Due to the union-subclass mapping, there is no foreign key for the entity lookup. So you can basically misuse the lookup and reference a proxy object which actually must not exist (e.g. record in a different table) and this happens in our application as well in some of our unit tests.
Data creation:
var entitySCIL = new EntityWithSuperClassInterfaceLookup
{
Id = Guid.NewGuid(),
Name = "Name 1",
//EntityLookup = (IAbstractEntity)session.Load(typeof(SubEntityInterfaceProxy), subEntityIP.Id)
// by using a wrong id, the id query throws of course a database exception
EntityLookup = (IEntity)session.Load(typeof(SubEntityInterfaceProxy), Guid.NewGuid())
};
session.Save(entitySCIL);
Test code:
[Test]
public void LoadEntityWithSuperClassLookup()
{
using (var session = OpenSession())
{
var entity = session.Get<EntityWithSuperClassInterfaceLookup>(_entityWithSuperClassLookupId);
Guid id = entity.EntityLookup.Id;
}
}
Case 1: Base class uses the class itself as proxy definition
When you query the object and access the id of the lookup proxy a query is triggerd which leads to the following exception:
Test Name: LoadEntityWithSuperClassLookup
Test FullName: NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.Fixture.LoadEntityWithSuperClassLookup
Test Source: C:\git\NHibernate\src\NHibernate.Test\NHSpecificTest\IlogsProxyTest\ProxyTypeEvaluation\Fixture.cs : line 112
Test Outcome: Failed
Test Duration: 0:01:41,957
Result StackTrace:
bei NHibernate.Impl.SessionFactoryImpl.DefaultEntityNotFoundDelegate.HandleEntityNotFound(String entityName, Object id) in C:\git\NHibernate\src\NHibernate\Impl\SessionFactoryImpl.cs:Zeile 86.
bei NHibernate.Proxy.AbstractLazyInitializer.CheckTargetState() in C:\git\NHibernate\src\NHibernate\Proxy\AbstractLazyInitializer.cs:Zeile 245.
bei NHibernate.Proxy.AbstractLazyInitializer.Initialize() in C:\git\NHibernate\src\NHibernate\Proxy\AbstractLazyInitializer.cs:Zeile 111.
bei NHibernate.Proxy.AbstractLazyInitializer.GetImplementation() in C:\git\NHibernate\src\NHibernate\Proxy\AbstractLazyInitializer.cs:Zeile 175.
bei ISubEntityProxyProxy.get_Id()
bei NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.Fixture.LoadEntityWithSuperClassLookup() in C:\git\NHibernate\src\NHibernate.Test\NHSpecificTest\IlogsProxyTest\ProxyTypeEvaluation\Fixture.cs:Zeile 117.
Result Message: NHibernate.ObjectNotFoundException : No row with the given identifier exists[NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.EntityClassProxy#0f626ebf-fc05-4800-92c3-bfd7136cde50]
The proxy interfaces of the lookup look suspicious to me. Why is the lookup of type ISubEntityProxyProxy? Shouldn't it derive from EntityClassProxy? And why are all sub interfaces returned (e.g. IAnotherSubEntityProxy)?
entity.EntityLookup.GetType().ToString()
"ISubEntityProxyProxy"
entity.EntityLookup.GetType().GetInterfaces()
{System.Type[5]}
[0]: {Name = "ISerializable" FullName = "System.Runtime.Serialization.ISerializable"}
[1]: {Name = "INHibernateProxy" FullName = "NHibernate.Proxy.INHibernateProxy"}
[2]: {Name = "ISubEntityProxy" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.ISubEntityProxy"}
[3]: {Name = "IEntity" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.IEntity"}
[4]: {Name = "IAnotherSubEntityProxy" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.IAnotherSubEntityProxy"}
Case 2: Base class uses an interface as proxy definition
It works as stated in the documentation. You can access the id of the proxy object, without triggering a query.
The proxy looks basically good to me, except the interfaces (similar to above)
entity.EntityLookup.GetType().ToString()
"IEntityProxy"
entity.EntityLookup.GetType().GetInterfaces()
{System.Type[5]}
[0]: {Name = "ISerializable" FullName = "System.Runtime.Serialization.ISerializable"}
[1]: {Name = "INHibernateProxy" FullName = "NHibernate.Proxy.INHibernateProxy"}
[2]: {Name = "IEntity" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.IEntity"}
[3]: {Name = "ISubEntityProxy" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.ISubEntityProxy"}
[4]: {Name = "IAnotherSubEntityProxy" FullName = "NHibernate.Test.NHSpecificTest.IlogsProxyTest.ProxyTypeEvaluation.IAnotherSubEntityProxy"}
I've attached a test fixture as well the mappings.
Thanks in advance!