Blog Archives
NHibernate – Lessons Learned
As usual doing a Hello World using a technology turns out to be quite simple. It’s only when you start getting into serious stuff, you run into issues & of course learn a lot. I am sharing few of my learning’s on NHibernate over last month. The DB I am using is Oracle which makes these observations more important as there is a lot available out there on NHibernate but only with SQL SERVER.
1) Optimistic locking: NHibernate has an element called <version>, defined right after ID which can handle concurrency for you. I used a TIMESTAMP column in oracle & mapped it to a DateTime property in my C# class.
<version column=”ROWVERSION” type=”DateTime” name=”RowVersion” />
Now when you insert an entity containing version as above, NHibernate would automatically generate the TimeStamp for you & on update it will also do an automatic comparison. If you need to catch the Exception arising out of this issue, it would be StaleObjectStateException.
2) Cascade issues: A good description of why / what are cascades can be found here, though there are still few issues you will run into. Consider an entity called Class, which has a many-to-one relationship with professor.
<many-to-one name=”Professor” column=”A_PROFESSORID” cascade=”none” lazy=”false” />
For unidirectional associations like many-to-one (there is no one-to-many back), it makes sense to assign “none” to cascade, otherwise you will see superfluous updates. In this scenario, whenever you modify class attributes / properties, it will fire updates for Professor.
Though you still may not be able to completely get rid of superfluous updates. Let me put up one more scenario. I have a Department & I have a Class (one-to-many). Now for this you would keep cascade=”all” for obvious reasons as a save / update /delete to Department should also save / update / delete Classes. But if you modify only a single property of department you will still end up updating all classes
. I hope such superfluous updates will be removed with upcoming versions.
In case you require a soft delete (which is the case for many applications, maintaining an IsActive Flag), you can keep cascade option as “Save-Update” in parent (one-to-many).
3) BiDirectional / UniDirectional navigational relationships / associations: If you want to define a one-way only traversal through NHibernate you may not be quite successful. Say, a department has classes (one-to-many). Now if your DB (Domain) says that classes can’t stand on their own they have to always belong to a department then you are in a fix. You must provide a bidirectional reference so that whenever you create a new instance of a class, you can bind it to Department.
E.g.
public virtual void AddUniversityClass(UniversityClass universityClass)
{
if(_classes == null)
_classes = new ObservableList();
universityClass.Department = this; //BiDirectional, Class has a department & department has classes
_classes.Add(universityClass);
}
If you don’t do above your insert of Class would fail as they would have a null foreign key for Department. Sounds obvious but looks more like database structure affecting your object model (navigation), which you normally don’t want. Yet some may argue in favor of it, citing benefits of explicit constraints. One way could be making your navigation relationship (many-to-one for instance) private.
4) For collections don’t forget inverse=”true”: Why? I could have elaborated but it’s already explained here. This tells that responsibility for updating the column value is on the other side (just specifying inverse=’true’ on the collection <one-to-many> association).
5) Oracle supports batching through adonet.batch_size: This was confirmed to me by Ayende and Tomer. Batching would club the CRUD statements & send them to DB at one shot giving decent performance gain. The only thing I don’t like here is the attribute name. After all if the batching is done for oracle also, why call it adonet.batch_size? It was confusing for me atleast
. Also this works only with ODP.NET.
6) Use fetch=”join” for one-to-many & many-to-one relationships: This is to avoid select N + 1 problem. In my infancy stage of programming I use fall into this trap (limited knowledge of SQL
). N.B. outer-join attribute is deprecated.
7) Always use Criteria queries unless you need to do a projection: Criteria Queries are easier to read, they leverage on configuration files (e.g. fetch=”join”), & they safely handle parameters (developers run into concatenation of parameters using HQL). But alas you can’t use Criteria Queries everywhere. For instance if you need to fetch only few fields of a given entity (no associations, only selected properties for a lookup display), HQL is the only way out.
E.g.
string hqlQuery = “select new Department(D.ID, D.Name) from Department as D”; // Your class needs to have this constructor along with the default one
IQuery _query = Session.CreateQuery(hqlQuery);
IList _list = _query.List();
var observablelist = new ObservableList();
foreach (Department department in _list) { observablelist.Add(department); }
N.B. Projections are of utmost importance in NHibernate. Try to avoid bringing any unnecessary data to the client even if you have to resort to untyped objects. You can use a reader like loop to create your typed object graph & throw that graph back to client.
8 ) Many-To-Many doesn’t work with extra columns: Many-To-Many is normally expressed in database with help of third table. Though NHibernate doesn’t require a corresponding class for the third (mapping) table, but if third table contains additional columns other then the primary keys of involved tables, NHibernate won’t be able to support them. Solution we found was to move those additional attributes from the third table to the involved tables.
9) Components are of good value: To be frank I ignored the components completely when I started with NHibernate. In fact the whole concept of Entity vs. Value objects wasn’t clear to me even after going through DDD books. I mean, I got Value objects don’t require an identity but how does that affect my programming. But boy, I was wrong. I could have elaborated on the same here, but would request you to go through Hibernate in Action section 3.5 (starting page 92-93).
10) unsaved-value is important: You can use it to specify default values for columns. NHibernate also uses it to distinguish between new & modified entities.
E.g. Session.SaveOrUpdate(“Department”, department); //unsaved value would be used here to determine whether to do an insert or update. My Id reads as below for Oracle:
<id name=”ID” column=”A_ID” unsaved-value=”0″>
<generator class=”Infrastructure.GenerateID, Infrastructure” /><!– GenerateID is a class that implements IIdentifierGenerator interface, in my case it returns a GUID. There is no identity column in Oracle !–>
</id>
11) NHibernate supports database views: You can have a mapping file point to a database view as well. I normally use it for summary display (complex queries). Ensure you check your performance before getting into it (especially no where clauses attached). Views typically won’t have an primary column but your mapping file would still required one for ID element. So you can pick a column of your choice which is unique and make it part of ID by removing the same from property mapping. You can point generator class to native (an easy way to get away).
<id name=”_year” column=”YEAR” unsaved-value=”0″ access=”field”>
<generator class=”native” />
</id>
12) Parameterized Queries: If you are using NHibernate ideally you would talk to underlying DB with Criteria queries, HQL or raw SQL. NHibernate supports safe parameter passing in all of them as show below:
a) Criteria Queries: ICriteria criteria = Session.CreateCriteria(typeof(Post), “P”).Add(Expression.Eq(“P.PostId”, postId));
b) HQL or Raw SQL: s.CreateQuery(“from Post p where p.Title = :title”).SetString(“title”,”NHibernate”).List();
You also need to add a line to NHibernate configuration as discussed here – <property name=’prepare_sql’>true</property>
13) NHProf: Finally you should use NHProf to improve your understanding on the way NHibernate works.
I would appreciate your observations on above points.
TFS Common issues
Below are few issues which I guess one would run into on their first usage of TFS & Team Explorer.
1) Permanently deleting Dummy Projects: After playing around for a while there would few dummy Team Projects created. By default TFS uses a soft delete. For permanent (hard) delete one can use tf command line utility with destroy option.
Note if you have deleted a project already you need to undelete it & check in pending changes. Destroy doesn’t work on deleted projects. Also the folder you are trying to delete should be mapped to a workspace (File -> Source Control ->Workspaces…)
E.g. tf destroy $/YourProjectName/SubFolder/… /collection:http://myserver:8080/tfs/defaultcollection (for other options check out tf destroy /?)
2) Logging in as a different user: By default VS.NET would ask you credentials to connect to TFS every time you run it. You can avoid it by caching required credentials. To cache your credentials go to Control Panel -> User Accounts -> Manage your network password (left column) -> Click Add to add the required details. Once added VS.NET won’t trouble you for credentials.
3) Deleting a workspace: Workspace belongs to a owner (authenticated user by TFS). Let’s say you have logged in as Admin & set your working folder to C:\WorkingFolder. Now you want to log on TFS as local user (without admin rights) & use the same mapped path (C:\WorkingFolder). TFS at this level would complain Admin is already using that location, hence you can’t use it. In order to remove workspace created by Admin you again need to fallback on tf utility.
E.g. tf workspace /delete MyWorkspace;MyDomain\Admin /server:http://MyTFS:8080/tfs
4) Automatic Check Out – not working: If you go to VS.NET Tools -> Options -> Source Control -> Environment, you will see 2 drop downs there. There is one which reads Editing – Check Out Automatically. What this means is when you have a opened project & you edit files using Solution Explorer those would be checked out automatically. But sometimes you won’t find it working. A possible reason, your solution is not binded to source control. To regain the bindings click on File -> Source Control -> Change Source Control (N.B. At this point of time Source Control explorer should be closed). There you will a list of your projects & solutions. Select them & click on bind button on the tool bar window. Things would start working now as expected. When you bind the TFS project and solution by default they will be checked out and 2 additional files vspscc and vssscc would be created. You need to check-in the project (.csproj) / solution (.sln) files to avoid rebinding of the solution next time. There isn’t a need to include vspscc and vssscc inside TFS.
5) Unlocking a file: 2 steps – find the workspace that belongs to the user and then execute undo command by specifying workspace, user account and file path -
tf workspaces /owner:domain\userid –you would get these parameters from the file lock message
tf undo /workspace:workspacename;domain\userid $/filePath –filePath you can copy it from File Properties in Source Control
6) Permanently deleting a WorkItem: There is quite a possibility there your team could create dummy workitems in process of getting familiar with the system. At times it might be important to cleam this items so that they don’t impact your charts and reports. Below is the command you can use to delete a workitem permanently.
witadmin destroywi /collection:CollectionURL /id:id [/noprompt]
Hope above saves your hair
.
