Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

NHibernate Get vs Load and Sub Types

DZone's Guide to

NHibernate Get vs Load and Sub Types

· Database Zone
Free Resource

Learn how to create flexible schemas in a relational database using SQL for JSON.

 

 

Here’s some interesting NHibernate behaviour that’s been tripping me up. Say we have a base class ‘Content’ and a sub class ‘Menu’, the following tests pass:

 

[Test]
public void Get_returns_a_proxy_of_the_correct_type()
{
InSession(session =>
{
var content = session.Get<Content>(menu1Id);
var menu = content as Menu;
menu.ShouldNotBeNull("menu is null");
});
}

[Test]
public void Load_does_not_get_return_a_proxy_of_the_correct_type()
{
InSession(session =>
{
var content = session.Load<Content>(menu1Id);
var menu = content as Menu;
menu.ShouldBeNull();
});
}

That’s right, don’t expect ‘as’ to work on lazily loaded entities.

Get eagerly loads, so it runs some SQL against the database:

SELECT 
content0_.ContentId as ContentId15_0_,
content0_.Name as Name15_0_,
content0_.Position as Position15_0_,
content0_.IsActive as IsActive15_0_,
content0_.UrlName as UrlName15_0_,
content0_.ParentContentId as ParentCo7_15_0_,
content0_.ContentTypeId as ContentT8_15_0_,
content0_.Controller as Controller15_0_,
content0_.Action as Action15_0_,
content0_.Text as Text15_0_,
content0_.ContentType as ContentT2_15_0_
FROM "Content" content0_
WHERE content0_.ContentId=@p0;@p0 = 1 [Type: Int32 (0)]

This includes the discriminator column, ContentType, so NHibernate knows that this item should be created as a menu.

Load, lazy loads, so for this example no SQL is run against the database. The only thing NHibernate knows about this entity is that we’ve asked for ‘Content’ via the type parameter of the Load method, so it returns a proxy class that inherits from Content. When we use the ‘as’ operator to cast it to a menu it fails (returns null).

Interestingly, the type is still incorrect after we examine some properties so that the proxy is populated with a call to the db:

[Test]
public void Type_is_not_correct_after_the_entity_loaded()
{
InSession(session =>
{
var content = session.Load<Content>(menu1Id);
Console.WriteLine(content.Name);
var menu = content as Menu;
menu.ShouldBeNull();
});
}

Event though this test causes the select statement to be executed against the DB to populate the entity, the proxy is still of type Content.

So rather than using the ‘as’ operator, I’ve implemented my own CastAs<T> extension method that understands NHibernate proxies:

public static class CastExtensions
{
public static T CastAs<T>(this object source) where T : class
{
if(source is INHibernateProxy)
{
var type = NHibernateUtil.GetClass(source);
if (type != typeof (T))
{
throw new ApplicationException(string.Format("Cannot cast {0} to {1}", type.Name, typeof (T).Name));
}

return ((INHibernateProxy) source).HibernateLazyInitializer.GetImplementation() as T;
}
return source as T;
}
}

Now the we get the correct sub type:

[Test]
public void Use_CastAs_to_cast_lazy_loaded_entities()
{
InSession(session =>
{
var content = session.Load<Content>(menu1Id);
Console.WriteLine(content.GetType().Name);
var menu = content.CastAs<Menu>();
menu.ShouldNotBeNull();
});
}

Update

As Mookid pointed out in the comments, doing this kind of casting, especially of domain classes, is a code smell. What I should be doing instead is using polymorphism or the visitor pattern. Please read his post below which is a very cool alternative take on the problem:

http://mookid.dk/oncode/archives/991

 

Create flexible schemas using dynamic columns for semi-structured data. Learn how.

Topics:

Published at DZone with permission of Mike Hadlow, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}