Attention: We are retiring the ASP.NET Community Blogs. Learn more >

Nhibernate: Component set to null if all component columns are null

This post is in regards to the post I saw unanswered on Nhibernate forums since Mon Feb 12, 2007 1. 

When reloading the containing object, Hibernate will assume that if all component columns are null, then the entire component is null.

If you dive through the source code it occurs somewhere here.

1. if (ReturnedClass.IsValueType)

2. return values;

3. else

4. return notNull ? values : null;

Easy(but not advisable) solution is to alter source code like this.

   return values. (removed lines 1, 3 and 4 above).

 

This solution is not advisable as with every version change in Nhibernate you will have to recode this piece(if, in case, if it not fixed by the team :) ).

That's not the end to it. This could be fixed if we create a custom interceptor and override a method called OnLoad(). However, later in class TwoPhaseLoad it again changes the component object to null at the following line.

 persister.SetPropertyValues(entity, hydratedState, session.EntityMode);

For test purposes I am using Northwind database.

The solution is below. However, this solution is beneficial only if you are trying to implement a custom interceptor. If not, go to the bottom of the post.

public class Customer

{

virtual public string CustomerId

{ get; set; }

virtual public string CompanyName

{ get; set; }

virtual public Address Address{ get; set; }

 

}

 

public class Address

{

virtual public string City

{ get; set; }

virtual public string Region{ get; set; }

}

 

Customer.hbm.xml file:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"

assembly="NhibTest" namespace="NhibTest">

<class name="Customer" table="Customers">

<id name="CustomerId" type="string">

 

</id>

<property name="CompanyName" />

<component name="Address">

<property name="City"/>

<property name="Region"/>

</component>

</class>

  </hibernate-mapping>

 nhibernate.cfg.config file:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

<session-factory>

<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>

<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>

<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>

<property name="connection.connection_string">Data Source=ggrubbs\sqlexpress;Initial Catalog=Northwind;Integrated Security=True</property>

<property name='proxyfactory.factory_class'>NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>

//Please make sure you add this line.

<property name="show_sql">true</property>

<property name="generate_statistics">true</property>

</session-factory>

</hibernate-configuration>

MyInterceptor.cs

public class MyInterceptor : EmptyInterceptor

{

private ISessionFactory _sessionFactory;

private ISession _session;

private object _entity;public MyInterceptor(ISessionFactory sessionFactory)

{

_sessionFactory = sessionFactory;

}

 //Overriding this method also solves the problem.public virtual void AfterTransactionCompletion(ITransaction tx)

{

var stats = _session.Statistics;

var sessionImplementor = _session.GetSessionImplementation();

IPersistenceContext persistenceContext = sessionImplementor.PersistenceContext;

EntityEntry entityEntry = persistenceContext.GetEntry(_entity);for (int i = 0; i < entityEntry.LoadedState.Length; i++)

{

if (entityEntry.Persister.PropertyTypes[i].IsComponentType && entityEntry.Persister.PropertyNames[i].Equals("Address"))

{

if (_entity != null && _entity is Customer)

{

if ((_entity as Customer).Address == null)

{

(_entity
as Customer).Address = new Address();

}

}

}

}

}

}

 

Program.cs file:

_configuration = new Configuration();

_configuration.Configure();

_configuration.AddAssembly(
typeof(Customer).Assembly);

_sessionFactory = _configuration.BuildSessionFactory();

 

ISession session = _sessionFactory.OpenSession(new MyInterceptor(_sessionFactory));

There is also a very easy way of solving this problem.
Just make sure you add the following check in your component types.

protected Address _address;virtual public Address Address

{

get { return _address ?? (_address = new Address()); }

set { _address = value; }

Please let me know if you need the VS 2010 project. Please leave your email in the comment fields.


No Comments