Saturday, January 29, 2011

Nhibernate: Send update queries even when no change made.

Disclaimer:  The test shown here may not be the best practice.

In my last post I showed a bizarre behavior of Nhibernate. Here is what I wrote,

When we flush and commit an ISession in Nhibernate, it goes to database to flush all changes even if you just make nothing but some select queries.

It turns out, it is not Nhibernate behaving weird but I have managed to planchet a ghost in my code.

The statement is correct for the first part but Nhibernate does not have to go to database if you just do some select queries.

Fabio Maulo himself addressed this issue in one of his blog posts “How Test your mappings: the Ghostbuster” 

After reading his post and a Stackoverflow Q/AI have managed to find out the problem.

Here is the mapping file of DineInTable class:

<class name="DineInTable">
    <property access="property" type="int" name="DisplayIndex"/>
    <property access="property" type="int" name="MaxGuest"/>
    <property access="property" type="int" name="TableState"/>
    <property access="property" type="bool" name="Smoking"/>
</class>

And the class definition:

    public class DineInTable
    {
        public virtual int DisplayIndex { getset; }
        public virtual int MaxGuest { getset; }
        public virtual int TableState { getset; }
        public virtual bool Smoking { getset; }
    }

As you can see in the mapping file all these properties are nullable but in the class definition they are not. I want to quote this Stackoverflow Answer which explains this very problem precisely.

The following sequence explains why this happens: 

1) Nhibernate retrieves raw entity's data from DB using ADO.NET
2) Nhibernate constructs the entity and sets its properties
3) If DB field contained NULL the property will be set to the defaul value for its type:
4) properties of reference types will be set to null
5) properties of integer and floating point types will be set to 0
6) properties of boolean type will be set to false
7) properties of DateTime type will be set to DateTime.MinValue  etc
Now, when transaction is committed, NHibernate compares the value of the property to the original field value it read form DB, and since the field contained NULL but the property contains a non-null value, NHibernate considers the property dirty, and forces an update of the enity.

So, that is exactly what is causing all those update queries. To fix the problem either I can make these properties Nullable in the class definition or can set not-null=true in the mapping file. In this case I do not want them to be null so went with not-null=true.

Here is the test again:
        [Test]
        private void CanGetDineInTableByGroup()
        {
           
            TableGroup tableGroup = this._dineInTableModel.GetTableGroupById(
                    new Guid("11111111-1111-1111-1111-111111111111"));
            var dineInTables = this._dineInTableModel.GetTablesByGroup(tableGroup);
            this._dineInTableModel.CloseConversation();

            Assert.IsNotNull(tableGroup);
            Assert.IsNotNull(dineInTables);           
          
        }

And the result:

CanGetDineInTableByGroup : Passed                                                      
*** ConsoleOutput ***
NHibernate: SELECT tablegroup0_.TableGroupId as TableGro1_0_0_, tablegroup0 …
NHibernate: select dineintabl0_.DineInTableId as DineInTa1_3_, dineintabl0  ….

Voila! Just two select queries as I expect.

Conclusion: 
Some very intelligent people wrote Nhibernate. In most cases you are the stupid one if you see something happing weird.  Kudos to TDD, without it, it would have been very difficult to find even this problem in the first place.

“Learning by test”


Wednesday, January 26, 2011

NHibernate.TransientObjectException: object references an unsaved transient instance

***Edit: please see the next post where i explained this behavior.

When we flush and commit an ISession in Nhibernate, it goes to database to flush all changes even if you just make nothing but some select queries***. Here is an experimental test case and its result:
        [Test]
        private void CanGetDineInTableByGroup()
        {           
            TableGroup tableGroup =      this._dineInTableModel.GetTableGroupById(
                    new Guid("11111111-1111-1111-1111-111111111111"));
var dineInTables = this._dineInTableModel.GetTablesByGroup(tableGroup);

            this._dineInTableModel.CloseConversation();

            Assert.IsNotNull(tableGroup);
            Assert.IsNotNull(dineInTables);           
           
        }

Here is the result:

*** ConsoleOutput ***
NHibernate: SELECT tablegroup0_.TableGroupId as TableGro1_0_0_, tablegroup0 …
NHibernate: select dineintabl0_.DineInTableId as DineInTa1_3_, dineintabl0 …

NHibernate: UPDATE DineInTable SET RowVer = @p0, DineInTableName, …
 NHibernate: UPDATE DineInTable SET RowVer = @p0, DineInTableName = @p1,…
NHibernate: UPDATE DineInTable SET RowVer = @p0, DineInTableName = @p1,… 
NHibernate: UPDATE DineInTable SET RowVer = @p0, DineInTableName = @p1,… 

I am using uNHAddins here. CloseConversation is a convenient method to call session.Flush() and transaction.Commit() entirely controlled by uNHAddins.

As you can see, there are four update queries.  Anyone knows why, please leave a comment.

Another important “gotcha “, after closing a conversation/flush & commit, if you try to execute query in a different Session with detached object of previous Session you will probably get an error like this:


        [Test]
        private void CanGetDineInTableByGroup()
        {
           
            TableGroup tableGroup = this._dineInTableModel.GetTableGroupById(
                    new Guid("11111111-1111-1111-1111-111111111111"));
var dineInTables = this._dineInTableModel.GetTablesByGroup(tableGroup);

            this._dineInTableModel.CloseConversation();

IList<DineInTable> tablesByGroup = this._dineInTableModel.GetTablesByGroup(tableGroup);
        
            Assert.IsNotNull(tablesByGroup);
            Assert.IsNotNull(tableGroup);
            Assert.IsNotNull(dineInTables);           
          
        }


CanGetDineInTableByGroup : Failed
*** Failures ***
Execute
NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing. Type: Xenon.XenonPOS.DomainModel.Entities.TableGroup, Entity: Xenon.XenonPOS.DomainModel.Entities.TableGroup


Here is what you need to make the test pass:

        [Test]
        private void CanGetDineInTableByGroup()
        {
           
            TableGroup tableGroup = this._dineInTableModel.GetTableGroupById(
                    new Guid("11111111-1111-1111-1111-111111111111"));
var dineInTables = this._dineInTableModel.GetTablesByGroup(tableGroup);

            this._dineInTableModel.CloseConversation();

            this._dineInTableModel.SaveOrUpdateTableGroup(tableGroup);

IList<DineInTable> tablesByGroup = this._dineInTableModel.GetTablesByGroup(tableGroup);
        
            Assert.IsNotNull(tablesByGroup);

            Assert.IsNotNull(tableGroup);
            Assert.IsNotNull(dineInTables);           
          
        }

SaveOrUpdateTableGroup() is actually opening a brand new ISession and attaching detached tableGroup to that session. Rest of the methods is sharing this same new Session.