I'm using NHibernate 3.2 and I have a problem with mapping a collection.
After querying a user account...
UserAccount userAccount = (from u in Session.Query<UserAccount>()
where u.Username == username
select u).SingleOrDefault();
... the Role
property holds an empty collection while the roles
field contains actual items:
Is this a bug in NH 3.2 or something? I'm pretty sure it did work on 3.1.
I figure something might be wrong with the proxy behavior, specifically the override that handles lazy loading for the Roles
property. However, I don't know how to inspect the generated proxy class (using an IL decompiler) because as far as I know it lives only in memory at run time.
EDIT 1
To help me find out what's going on inside the proxy, I've just posted this question: Is there a way to decompile the proxy classes that are generated by NHibernate?. I think the subject deserves to be a question on its own because it may be of use in many other situations.
EDIT 2
Well, I managed to decompile the dynamic proxy class. This is the method that manages lazy loading for the Roles
property:
public override IEnumerable<Role> get_Roles()
{
IInterceptor interceptor = this.Interceptor;
if (interceptor == null)
{
throw new NotImplementedException();
}
object[] args = new object[0];
InvocationInfo info = new InvocationInfo(
this, (MethodInfo)methodof(UserAccount.get_Roles),
null, new Type[0], args);
args = info.Arguments;
return (IEnumerable<Role>)interceptor.Intercept(info);
}
I don't think anything special is going on here though.
EDIT 3
While debugging the interceptor (which is partially shown below), I noticed that when it is called for methodName == "get_Roles"
, for the TargetInstance
property (which is a UserAccount
in this case), it's roles
field is an empty collection. Right before accessing the Roles
property on the proxy instance though, the proxy's role
field does have a populated collection.
public class DefaultDynamicLazyFieldInterceptor
: IFieldInterceptorAccessor, Proxy.DynamicProxy.IInterceptor
{
...
public object Intercept(InvocationInfo info)
{
var methodName = info.TargetMethod.Name;
if (FieldInterceptor != null)
{
...
When checking the InvocationInfo
instance, the proxy
and the Target
instances do have a roles
field, containing a populated collection.
EDIT 4
I think that I've ran into NH Issue 2772 - Lazy-collection not loaded when a property is Lazy-loaded.
END OF EDITS
Here are the entity classes:
public class UserAccount : Entity
{
...
private IList<Role> roles;
public virtual IEnumerable<Role> Roles
{
get
{
return roles;
}
}
...
}
public class Role : Entity
{
...
}
Here's a part of the HBM mapping file for the UserAccount mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property"
auto-import="false" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" dynamic-insert="true"
dynamic-update="true" schema="[MySchema]" mutable="true"
name="MyNamespace.UserAccount, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
table="UserAccounts">
...
<bag access="nosetter.camelcase" cascade="none" name="Roles"
schema="[MySchema]" table="UserAccounts_Roles" mutable="true">
<key>
<column name="UserAccountId" />
</key>
<many-to-many class="MyNamespace.Role, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<column name="RoleId" />
</many-to-many>
</bag>
...
</class>
</hibernate-mapping>
And here's part of the HBM that specifies the Role mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property"
auto-import="false" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" dynamic-insert="true"
dynamic-update="true" schema="[MySchema]" mutable="true"
name="MyNamespace.Role, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
table="Roles">
...
</class>
</hibernate-mapping>
I just checked the 2772 issue and apparently it has been fixed, set to be released in the next major version of NHibernate (which is planned to be called 4.0). Therefore I'm marking the question as answered, albeit a little premature.