Why NHibernate and ORM:
Since I've started web programming I used to use shared hosting to host my web applications, which noticeably slow down the application performance, that always made me care about performace issues, like:
Always Using stored procedures, custom database paging, only select the needed columns when binding results to tabular controls (-> repeaters) and etc.. in fact that was pretty cool but I always face a problem of time management.
When my company started to go into more complex application I found out that with enterprise applications time is more important and code design with organizing are more handy and we always need a reliable memory resources. So now my mession is to think more about time and less about performace, so the best was using ORM.
NHibernate is the best, because handling data access are more time consuming than handling business rules, so NHibernate, here, beats CSLA .Net, I am not going deeply into NHibernate now but it is very cool for me;)
What is Codesmith?
Always Nhibernate or ORM terms come with generation tool.The real easy task that nhibernate provides the real boring creating its classes and the mapping files, so if you do not wish to use generators, you'd better not use ORM.
Codesmith is the most famous generation tool for most famous design models including Nhibernate and CSLA.
Codesmith generates classes,xml files, stored procedures and really everything you wish, using its provided templates or your own ones.
Building templates is the most complex thing in codesmith, so it is not the time for discussing how to build your template, soon i will be posting an article discussig how to create or customize your own template easily...
A template for NHibernate is shipped with codesmith but unfortunetly it has some bugs and really not reliable. So i decided to fix the problem and publish my point of interest with it.
What should we generate?
We should generate a class and an xml mapping file (maps an object-oriented domain model to a traditional relational database) for every entity.
We have three templates, one for generating classes, another for mapping files and the third for looping the database and executes both templates to generate mappings and classes for the database.
HBM generation template:
A mapping file would be like this:
< ?xml version ="1.0" encoding ="utf-8" ? >
< hibernate-mapping xmlns ="urn:nhibernate-mapping-2.2">
< class name ="MyNamespace.Data.Agent, MyApp.MyAssembly" table ="Agents" lazy ="false">
< id name ="AgentID" type ="Int32" unsaved-value ="null">
< column name ="AgentID" length ="4" sql-type ="int" not-null ="true" unique ="true" index ="PK_Agents"/>
< generator class ="native" />
</ id >
< property name ="AgentName" type ="String">
< column name ="AgentName" length ="64" sql-type ="nvarchar" not-null ="true"/>
</ property >
< property name ="AgentLogo" type ="String">
< column name ="AgentLogo" length ="256" sql-type ="nvarchar" not-null ="false"/>
</ property >
< bag name ="Companies" inverse ="true" lazy ="true" cascade ="all-delete-orphan">
< key column ="AgentID"/>
< one-to-many class ="MyNamespace.Data.Company, MyApp.MyAssembly" />
</ bag >
</ class >
</ hibernate-mapping >
Id Maps the primary key, property maps other columns and bag includes mappings for relations (one-to-one; one-to-many; many-to-one; many-to-many).
Fixing:
1-For NHibernate 1.2 change the root mapping element to:
< hibernate-mapping xmlns ="urn:nhibernate-mapping-2.2">
2-Attribute lazy="false" should be applied to avoid lazy initialization of the type, although NHibernate can use lazy initialization using dynamic proxies, for more info read about castle project...
3-In Nhibernate.inc file the method CollectionName() gets the name of the collection by making table names plural.
It is a good work using regular expression to make the noun plural but it is not suitable here becoz according to standerizations db table names are plural, so it is ugly making a collection named Orderses rather than Orders.
4- There is really a bug in many to many relation generation, I am not sure if this template was really well tested but what makes me wonder that it is shipped with codesmith as it is :(.
< % if (IsManyToManyTable(primaryKey.ForeignKeyTable)) { % >
< bag < %= CollectionManyToManyNameAtt(primaryKey)% > < %= CollectionTableAtt(primaryKey)% > inverse="false" lazy="true" cascade="none">
< key >
< % foreach(ColumnSchema column in primaryKey.ForeignKeyMemberColumns) { % >
< column < %= ColumnNameAtt(column) % > < %= ColumnLengthAtt(column) % > < %= ColumnSqlTypeAtt(column) % > < %= ColumnNotNullAtt(column) % > < %= ColumnUniqueAtt(column) % > < %= ColumnIndexAtt(SourceTable, column) % > />
< % } % >
</ key >
< % foreach(TableKeySchema tableKey in primaryKey.ForeignKeyTable.ForeignKeys) { % >
< %if (tableKey.PrimaryKeyTable ! = SourceTable) { % >
< many-to-many < %=CollectionManyToManyClassAtt(tableKey) % >
< % foreach(ColumnSchema column in tableKey.ForeignKeyMemberColumns) { % >
column=" < %=ColumnName(column) % > "
< % } % >
/>
< % } % >
< % } % >
</ bag >
The fixed condition if (tableKey.PrimaryKeyTable != SourceTable) makes sure that only the collection for the table that is related as many to many is included.
5- IsManyToManyTable() method was fixed.So that detection of many to many tables are much more accurate.
We cannot really detect many to many table 100% accuretly but we depend on the usual database desgin that consists of composite primary key with 2 member columns and the many-to-many table should reference the current table, I've added a condition to make sure that both primary key columns are foreign keys.
Class generation template:
1- In the class template the primary key memeber is named Id and if the property ForceId is set to true it has the name of the pk column, the template did not handle composite primary keys. If we have a non-many-to-many table with composite primary key e.g (CountryId, CityId) both mapped columns would be the same "CountryId" because it use the only the first member column of the key, so the fix should be like:
< % for(int x =0;x<SourceTable.PrimaryKey.MemberColumns.Count;x++) {% >
protected < %= SourceTable.PrimaryKey.MemberColumns[x].SystemType % > < %= IdMemberName(SourceTable,x) % > ;
< % } % >
And IdMemberName() method would be:
public string IdMemberName(TableSchema table, int x)
{
if (ForceId)
return "_id" ;
else
return MemberName(table.PrimaryKey.MemberColumns[x]) ;
}
2- In the CompareTo() method. An error will be generated if a table has a binary column since binary columns represented as byte[] array in C#, so we cannot call CompareTo() to the byte[] array , also it is not suitable to sort by binary columns so the following line of code shoud be added (see the line that is bold):
< %if( column.DataType ! = DbType.Binary){% >
case " < %= PropertyName(column) % > ":
< % if (column.AllowDBNull ) { % >
relativeValue = (this. < %= PropertyName(column) %> != null) ? this. < %= PropertyName(column) % > .CompareTo((( < %= ClassName(SourceTable) % > )obj). < %= PropertyName(column) % > ) : -1;
< % } else { % >
relativeValue = this. < %= PropertyName(column) % > .CompareTo((( < %= ClassName(SourceTable) % > )obj). < %= PropertyName(column) % > );
< % } % >
break;
So we do not compare binary columns.
Conclusion:
First thanks for codesmith and all people implemented the template despite the described bugs. I hope I have helped people newer to codesmith and have a great enthusiasm writing their NHibernate projects easily. I have not really tested this template well , but it is working nice enough with Northwind. So if you face any bug I have missed please , tell me I may fix it later.
You can download the template with a working project for NHibernate from attachments.
FixedNHibernate.rar (471.23 kb)
74a3713c-2614-4e78-8dac-afb48ea7198a|1|3.0