I would like to understand the reason why the not-found attribute of a
many-to-one mapping influences the proxying behaviour of such associations.
If the not-found attribute is set to ignore the many-to-one is not loaded lazyly
and not proxy is used. If the not-found attribute is set to exception
the many to one uses lazy loading and a proxy is used.
Is this by design? And if so, what is the reason for it
Here is an example to reproduce the effect. (BTW: I am working with a legacy schema,
that is why I have assigned keys (not sure if they have any effect on the stuff described here)
and I use an IInterecptor to track the saved stated.)
I have the following mappings and classes:
Code:
using PlayGround.Classes;
namespace PlayGround.Play
{
public class Dep:BaseEntity // BaseEntity just handles the IsSaved property as I have assigned keys
{
string _depName; int _depNo;
virtual public int DepNo { get { return _depNo; } set { _depNo = value; } }
virtual public string DepName { get { return _depName; } set { _depName = value; } }
}
}
<hibernate-mapping default-cascade="all" xmlns="urn:nhibernate-mapping-2.2"
default-access="field.camelcase-underscore" assembly="PlayGround" namespace="PlayGround.Play" >
<class name="Dep" >
<id name="DepNo" unsaved-value="0">
<generator class="assigned" />
</id>
<property name="DepName" />
</class>
</hibernate-mapping>
namespace PlayGround.Play
{
public class Emp : BaseEntity
{
Dep _depNotFoundException; Dep _depNotFoundIgno; int _empId; string _lastName;
public virtual Dep DepNotFoundException { get { return _depNotFoundException; } set { _depNotFoundException = value; } }
public virtual Dep DepNotFoundIgno { get { return _depNotFoundIgno; } set { _depNotFoundIgno = value; } }
public virtual int EmpId { get { return _empId; } set { _empId = value; } }
public virtual string LastName { get { return _lastName; } set { _lastName = value; } }
}
}
<hibernate-mapping default-cascade="all" xmlns="urn:nhibernate-mapping-2.2"
default-access="field.camelcase-underscore" assembly="PlayGround" namespace="PlayGround.Play" >
<class name="Emp" >
<id name="EmpId" unsaved-value="0">
<generator class="assigned" />
</id>
<property name="LastName" />
<many-to-one name="DepNotFoundIgno" class="Dep" not-found="ignore" />
<many-to-one name="DepNotFoundException" class ="Dep" not-found="exception" />
</class>
</hibernate-mapping>
The following Test creates the output further down:
Code:
[Test]
public void PlayWithEmpsAndDeps()
{
SF.OpenNewLocalSession(); // recreates the schema
Console.WriteLine(new string('\n', 2));
SF.WithLocalDb(delegate(ISession s, ITransaction t)
{
Emp e = new Emp();
e.EmpId = 1;
Dep d = new Dep();
d.DepNo = 991;
Dep d2 = new Dep();
d2.DepNo = 888;
e.DepNotFoundException = d;
e.DepNotFoundIgno = d2;
s.Save(e);
t.Commit();
});
Console.WriteLine(new string('\n', 2));
SF.WithLocalDb(delegate(ISession s, ITransaction t)
{
Console.WriteLine("Test: Loading Emp");
Emp emp = s.Load<Emp>(1);
Console.WriteLine("Test: Type of returned object: " + emp.GetType().Name);
Console.WriteLine("Test: Accessing EmpId property");
Console.WriteLine("Test: " + emp.EmpId);
Console.WriteLine("Test: Accessing DepNotFoundIgno property");
Console.WriteLine("Test: " + emp.DepNotFoundIgno);
Console.WriteLine("Test: Type of prop DepNotFoundIgno: " + emp.DepNotFoundIgno.GetType());
Console.WriteLine("Test: Accessing DepNotFoundException property");
Console.WriteLine("Test: " + emp.DepNotFoundException);
Console.WriteLine("Test: Type of prop DepNotFoundException: " + emp.DepNotFoundException.GetType());
});
}
This is the output. I've added comments with //
Code:
// Schema creation output skipped
NHibernate: INSERT INTO Dep (DepName, DepNo) VALUES (@p0, @p1); @p0 = '', @p1 = '888'
NHibernate: INSERT INTO Dep (DepName, DepNo) VALUES (@p0, @p1); @p0 = '', @p1 = '991'
NHibernate: INSERT INTO Emp (LastName, DepNotFoundIgno, DepNotFoundException, EmpId) VALUES (@p0, @p1, @p2, @p3); @p0 = '', @p1 = '888', @p2 = '991', @p3 = '1'
Test: Loading Emp
//
// As Excpected: A proxy is created that just holds on to the id
Test: Type of returned object: CProxyTypePlayGround_PlayEmpPlay_NHibernate_ProxyINHibernateProxy1
//
// Funny, I thought accessing the Id property does not trigger a load.
// But as it turns out if you are not using the access strategy "property" this does not seem to work.
Test: Accessing EmpId property
NHibernate: SELECT emp0_.EmpId as EmpId127_0_, emp0_.LastName as LastName127_0_, emp0_.DepNotFoundIgno as DepNotFo3_127_0_, emp0_.DepNotFoundException as DepNotFo4_127_0_ FROM Emp emp0_ WHERE emp0_.EmpId=@p0; @p0 = '1'
//
// This is not as exspected.
// Quote from section 15.1.3:
// "By default, NHibernate 1.2 generates proxies (at startup) for all persistent classes and uses them to enable
// lazy fetching of many-to-one and one-to-one associations."
// But this is the many-to-one with the not-found="ignore" attribute. It behaves differently from the
// not-found="exception".
NHibernate: SELECT dep0_.DepNo as DepNo128_0_, dep0_.DepName as DepName128_0_ FROM Dep dep0_ WHERE dep0_.DepNo=@p0; @p0 = '888'
Test: 1
Test: Accessing DepNotFoundIgno property
Test: PlayGround.Play.Dep
Test: Type of prop DepNotFoundIgno: PlayGround.Play.Dep
Test: Accessing DepNotFoundException property
// This is the acccess to the property mapped with many-to-one not-found="exception". This one is really lazy loaded and uses a proxy.
NHibernate: SELECT dep0_.DepNo as DepNo128_0_, dep0_.DepName as DepName128_0_ FROM Dep dep0_ WHERE dep0_.DepNo=@p0; @p0 = '991'
Test: PlayGround.Play.Dep
Test: Type of prop DepNotFoundException: CProxyTypePlayGround_PlayDepPlay_NHibernate_ProxyINHibernateProxy1
Regards
Ralf