Disclaimer: The test shown here may not be the best practice.
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
“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”
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 { get; set; }
public virtual int MaxGuest { get; set; }
public virtual int TableState { get; set; }
public virtual bool Smoking { get; set; }
}
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:
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 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”
Had the same issue - this post really helped. Thanks!
ReplyDeleteGlad it helped you. I even forgot I have a blog!
Delete