I've has this post in my drafts for nearly a year now so I decided I'd finish it off.
I came across an issue when using the second-level cache in NHibernate where none of my objects were being cached and NHibernate was always going to the database for them. What was more odd was the NHibernate debug output suggested the items were being retrieved from the datbase and added to cache but on the next request the items couldn't be resolved in the cache.
Attatching a debugger and stepping through shows a timestamp passed into Cache's Put() method is equal to MinValue which makes it skip the Cache.Add at the last minute (whilst outputting debug messages to the contrary).
Tracking this timestamp back revealed it was created when the session is first openned and was due to my passing in my own connection string rather than letting NHibernate provide one. I assume this is a safeguard because IDs aren't necessarily unique between databases so in that case the cache would perform oddly.
If in your case they are however then you can work around this by casting your ISessionFactory as an ISessionFactoryImplementor. This has an extra overload of OpenSession which takes a IConnection and a ConnectionReleaseMode but doesn't disable the second-level cache.
Today I came across an NHibernate problem where I needed to select every instance of a particular base type and all its derived types from a database, apart from one particular derived type. Here is a trivial example:
public class Mammal {}
public class Dog : Mammal {}
public class Cat : Mammal {}
public class DomesticCat : Cat {}
In this case the problem was equivalent to selecting every mammal that isn't a domestic cat.
We're using the table per class hierarchy inheritance model in NHibernate which uses values in a discriminator column to determine which type is held in a particular row of the table.
ICriteria crit = sess.CreateCriteria(typeof(Mammal));
List mammals = crit.List();
I then needed to be able to effectively add a WHERE discriminator <> 'DomesticCat' to the end of the query. I had a quick search for this special discriminator property and for a Criteria Expression for excluding a particular type but couldn't find either.
The Solution
I finally found the solution on the WHERE clause page of the HQL chapter in the NHibernate reference. There is a special property called class which you can test against a type name in HQL or an actual type in Criteria queries e.g.
In HQL:
IQuery q = sess.CreateQuery("from Mammal m where m.class != 'DomesticCat'");
IList mammals = q.List();
This problem exists in Hibernate itself as well and, contrary to some comments I've seen in the bug tracker, I believe it is a bug in the Criteria API and not in HQL.
A trivial example
I have a Store type representing a shop which has a collection, Products, containing Product types the shop stocks e.g. "Golf Balls", "Bananas", "Hats" etc. I want to get all the stores who stock both Golf Balls and Hats.
In HQL this would be :
SELECT s
FROM Store AS s
INNER JOIN s.Products AS prod1
INNER JOIN s.Products AS prod2
WHERE prod1.Type = 'Golf Balls'
AND prod2.Type = 'Hats'
...again straight forward and seems logical but this produces an error...
NHibernate.QueryException: duplicate association path Products
As I said, it seems like a pretty solid candidate for a bug and it's odd considering surely the meaning of CreateAlias is that I want to use the same association more than once so need to alias it to different labels.
Unfortunately there's no way to get around this issue if you need distinct association joins like the above example and looking at the NHibernate code it doesn't seem like an easy fix. If, however, you can apply your criterion or sorts to the same alias then there is a workaround.
Workaround
If we take a look at this handy NHibernate API reference we see that the two implementing classes for ICriteria are NHibernate.Impl.CriteriaImpl and NHibernate.Impl.CriteriaImpl.Subcriteria.
CriteriaImpl is the root criteria you get calling CreateCriteria on ISession and Subcriteria you get with every call to CreateCrteria or CreateAlias on ICriteria.
First you need to retrieve the root CriteriaImpl for your working ICriteria. Your working ICriteria may be the root but if it isn't you need to recurse up through the Parent property until you reach the CriteriaImpl object.
CriteriaImpl has an IterateSubcriteria method which returns an IList of all its Subcriteria descendants. You can loop through this list checking the Parent and Path properties of each item. The Parent because the value of Path is relative and you're only interested in what will be sibling Subcriteria to the one you're about to add.
If you find a match you can retrieve its alias from the Alias property, otherwise you can add a new alias to your working ICriteria.
Update - Jan 2014
It seems this bug is still not fixed in NHibernate (or Hibernate for that matter) and that it may also affect the LINQ provider. The relevant issues links are:
I'm very tempted to have a go at fixing this myself given there still seem to be a few people struggling with it. Will post another update if I get anywhere.
If you create a lazy loaded property or collection in NHibernate which can contain any type from a class hierarchy, for example by having mappings like this:
You'll find that on requesting your objects from your collection they will be of a special new type NHibernate has created, derived from your base class which in this case is "Mammal".
This is a real problem because it means that you can't perform is or as operations on it to determine which actual type it is and you can't cast it to access properties and methods of your derived types.
The solution is to use the Visitor pattern which is described in detail on this site with a couple of examples in C# on this site. Essentially it involves creating a class with a method which is overloaded for each of the types in your class hierarchy.
class MammalVisitor
{
public void Visit(Cat c) { ... Cat operations ... }
public void Visit(DomesticCat dc) { ... DomesticCat operations ... }
public void Visit(Dog d) { ... Dog operations ... }
}
This "visitor" object is then passed to a method defined on the base class of your hierarchy and then subsequently overridden on each derived type.
class Mammal
{
public virtual void Accept(MammalVisitor mv) { mv.Visit(this); }
...
}
class Cat : Mammal
{
public override void Accept(MammalVisitor mv) { mv.Visit(this); }
...
}
class Dog : Mammal
{
public override void Accept(MammalVisitor mv) { mv.Visit(this); }
...
}
These methods simply call the visitor's method passing this to it which in turn will automatically execute the correct overload.
Mammal m; // some unknown derived type of mammal
MammalVisitor mv = new MammalVisitor();
m.Accept(mv);
These overloaded methods can then perform your type specific functions. In the example above you have no knowledge of the type of Mammal that you have however when you call Accept the relevant code is automatically executed. If Mammal happens to be a Cat type then the Cat overloaded Visit method is called.
If you fancy using NHibernate in VWD you'll have trouble because you can't compile your mapping files into the same assembly as your classes. As a result you can't add mappings to your session factory by assembly name or class name.
Thankfully there is a simple solution to this, which I'll demonstrate with the aid of the quickstart example in the NHibernate documentation.
Start by creating all your persistance class files in your App_Code folder with your mapping files (.hbm.xml) alongside. Next, make the alterations to your web.config as outlined in section 1.1 of the quickstart but leave out this line:
<mapping assembly="QuickStart" />
In your mapping files, change the assembly attribute of the root hibernate-mapping element to "App_Code" and remove the namespace attribute if you're not using a namespace (the default behaviour of VWD) e.g:
The original constructor used the mapping element in the web.config to find out which mappings to load, here we're telling it to load all the mapping files it finds in the App_Code folder. You can also use the AddFile method to add individual mapping files.