Tuesday, September 9, 2008

NHibernate - When the Fun Begins


So, you've integrated NHibernate as your ORM, you've created your objects and your mapping files, sooo... what next? The exciting part, that's what. I mean, I KNEW what an ORM was, what I didn't know is that my SQL would be taken care of for me, all LINQ-style in my code. What I didn't know was how manipulatable my objects would be with the functionality that NHibernate provide


IQuery: This is the most flexible, and frankly the least exciting, NHibernate data manipulation interface. It allows you to just run an inline query and return either a list, or a single object. To return a list you would just


    IList listIDs = session.CreateSQLQuery("select id from Foo where name = '" + name +"'").ToList();


 For a single item you would use .UniqueResult() instead of .ToList(). Something I know is possible but haven't used yet is the CreateMultiQuery method, which allows the return of multiple query results for different criteria. You can also use CreateQuery in a more direct way with your objects by just instantiating your object then passing it into CreateQuery() and using .Add to add parameters to your query. This is more direct, but I have not used it because of this beauty:


   ICriteria:    Ok, so listen to this, you just pass in the TYPE of the object you are looking for along with some criteria and it puts together the whole query for you. Like if I'm only trying to find Foos when Foo.Name is something like Steve and sort it by ID then I do this (notice the use of implicit local variables, 3.5 rulez!):


var foo = new Foo();


var baz = session.CreateCriteria(foo.GetType())


.Add(Restrictions.Like("Name", "%Steve%"))


.AddOrder(Order.Desc("id"))


.List();




 


How awesome is that? Restrictions is a member of Nhibernate.ICriterion and provides you with a way to add equations, or search for values to limit your result set. I used the ”like” constraint since it is so familiar to we SQL cats, but there are many more. It's that easy. You can also do simple inserts and updates using a simple session.Save() transaction which is super handy.




 


Now, before you play with any of this fun stuff you need to remember to do the following




 


-Build your SessionFactory (this I do here not in my helper class, this is stolen code from somewhere on the intarnetz, if someone can find it I will credit.


-Check for an existing Nhibernate ISession (NOT ASP.NET Session)


-Open a Nhibernate ISession (if one does not already exist)


-Then load your objects like above.




 


The funnest (I know, and I don't care) thing I have done so far is refactor the above behavior into a helper class. I am going to give it to you below because I love it and think it is great. Tell your friends, and have fun with Nhibernate (possibly also have fun with all the time you save, maybe earn some CrackOverflow points).




 


 


public class NHibernateObjectHelper


{


public static ISession session;




 


public static T LoadDataObject<T>(int Id)


{


session = CheckForExistingSession();


var returnObject = session.Get<T>(Id);


return returnObject;


}




 


public static IList<T> LoadObjectListAll<T>()


{


session = CheckForExistingSession();


var criteria = session.CreateCriteria(typeof(T));


var results = criteria.List<T>();


return results.ToArray();


}




 


public static ISession OpenDataObjectSession()


{


session = NHibernateSessionFactory.OpenSession();


return session;


}




 


public static ISession CheckForExistingSession()


{


if(session == null)


session = OpenDataObjectSession();


return session;


}




 




 


public static void CloseSession()


{


session.Close();


}




 


public static void Save(BaseObject<int> businessObject)


{


session = CheckForExistingSession();


var transaction = session.BeginTransaction();


session.Save(businessObject);


transaction.Commit();


}




 


public static void Update(BaseObject<int> businessObject)


{


session = CheckForExistingSession();


var transaction = session.BeginTransaction();


session.Update(businessObject);


transaction.Commit();


}


 




 


}



15 comments:

  1. I hope in reality you've got some try/finally or using statements so that the session gets cleaned up if anything throws an exception :)
    But yes, Hibernate/NHibernate rock. Is there any particular reason you're using the criteria API directly instead of the LINQ support? I would personally use the LINQ support for simple queries, and then craft things manually when I had to. It's always nice to get Intellisense and avoid possible typos :)
    Jon

    ReplyDelete
  2. Sweet, you are on the right track. Did you know you can parameterize those HQL queries?
    IList listIDs = session.CreateQuery(select id from Foo where name = + name).ToList();
    becomes
    IList listIDs = session.CreateQuery(select id from Foo where name = :name).SetParameter(name, name).ToList();
    Also, you might want to check out the HybridSessionBuilder (hybrid because it stores the ISesssion in HttpContext when using ASP.NET... it handles the checking for an existing session for you:
    jeffreypalermo.com/.../use-this-nhiber
    And the session-per-request module for making this automagic is here: dot-net-reference-app.googlecode.com/.../NHibernateSessi

    ReplyDelete
  3. @Jon - I'm not a big fan of the try...catch, in fact I don't use them at all. I think if my code is throwing an exception it should break outright, I haven't seen a benefit to the try catch yet, all the exception information is still contained in my exception log, and an error page is all I really need. Plus, there will be no exceptions, so this is a moot point :)
    I am not using LINQ for 3.5 because I am designing a hgh traffic site and the performance hit is supposed to be costly. If this is a special API for NHibernate I'm not familiar with it and would have to research.
    @Matt - Yes! That is a cool thing about the CreateQuery, I have been leaning towards CreateCriteria for that type of thing, however. I am using the session-per-request module but will have to take a look at HybridSessionBuilder for the checking. Thanks!

    ReplyDelete
  4. I think the reason you might want to do global exception handling is: When you do actually want to recover from an exception, you will need to destroy the current Hibernate session completely and start a new one, doing this in a centralized place can be helpful so that your client code doesn't need to worry about it, and can just deal with the exception however it sees fit.
    In my AbstractHibernateDao (similar to yours,) I do the clean-up, then re-throw the exception. This makes sure that you still get the exception downstream.
    Just a thought :)

    ReplyDelete
  5. @Lincoln - thanks for the tip! that's def something I will look into.

    ReplyDelete
  6. If you happen to use the .NET spring framework, all the basic NHibernate stuff is taken care of, including try-catch-finally, opening/closing sessions, transaction abstraction.
    The transaction abstractions are particularly nice so you can mix and match NHibernate, ADO.NET, and others in the same transaction if you need to.
    www.springframework.net

    ReplyDelete
  7. So I think you will want to use spring as well.
    You can also use AOP style around-advice to wrap each method with an en exception handler that will gracefully execute your desired exception handling the behaviour without the need to explicitly write try-catch blocks....
    if your learning ORM, be sure to read Scott Amblers cornerstone paper on the same: www.agiledata.org/.../mappingObjects.

    ReplyDelete
  8. Also, I don't dig having to implicitly call open and close when using hibernate. This should be combined with a pattern such as session-per-request and treated as an aspect... it.toolbox.com/.../implementing-hi

    ReplyDelete
  9. Thanks guys, that would be super handy, looking into the Spring Framework. What use is Agile if we can't quickly change things, huh?

    ReplyDelete
  10. Take it from someone who's used Hibernate for years now: it's great for 90% of your work, but the remaining 10% it sucks at will waste hours and days of your time. Read blogs.tedneward.com/.../The+Vietnam+Of+ to see what I mean.
    Hibernate specifically is notorious for unclear and misleading exception messages. It has a pretty bad user community in the sense that many people ask questions that are never answered and the ones that do get answered take the form of telling users off. Users literally get banned from the forums for uttering the word bug because the developers don't like people suggesting their code is buggy. There's a serious ego problem going on with those guys...
    Anyway, the biggest problem with Hibernate (and all ORMs I've seen for that matter) is that they are intrusive frameworks. They require architectural-level changes to your code in order to use them.
    It's also very easy to get sucked into using them. They'll work fantastic for small programs but the more you use them the more time you'll waste supporting ORM-specific issues and you wouldn't run into without them.
    I would seriously suggest you handle Hibernate with great care. I've personally migrated to Toplink (under Java) because they provide the same thing with far fewer bugs and a much friendlier community. At the end of the day I honestly believe that a strong friendly community is more important than what framework is more popular. You'll waste less time this way.

    ReplyDelete
  11. I agree with Ted Neward almost completely. Ted is talking about the Vietnam that people create for themselves due to over reliance and misuse of the tools they use.
    Hibernate is a fantastic tool for what it was designed for. I have used it for over 6 years now with great success. Hibernate has its limitations but if you know what they are (well known), you should not have a problem using it.
    If your database design sucks, Hibernate sucks. If your object models suck, Hibernate can't help you. If you try to use Hibernate for set based operations it will bring trouble (ADO.NET, hello). It will also suck if you try to marry crazy legacy schemas with idealistic object models. It will bite if you do not understand what it is doing behind the scenes - Hibernate is not a mind reader.
    Use it for identity based operations, you know, objects. Use it with a good OO design principles. Use it with a good data access layer. Know how to tune it with good defaults for YOUR application. Fine tune on a query level. Take advantage of caching that is built for you. Code a lot less and reap the benefits.

    ReplyDelete
  12. PS I see what you guys are saying about using a try...catch in this particular situation... I don't like the messyness of some of the transactions now that I have more than one person on it.... I will update with where I decide on going with this. Spring .net sounds cool but I think the question is how long will it take to implemnt?

    ReplyDelete
  13. So if you are familiar with Spring in general it shouldn't take long.
    Grasping Dependency Injection is not difficult. (Spring is nothing more than a DI container and framework at its most boiled down level)
    I think that you might invest a day or so into getting it all set up but that day is recovered when you think about the cost of forgetting a single close() call...Also the spring.net integration of hibernate is very slick and will definitely save you time if you are implementing hibernate.
    The vietnam of computer science stuph is great reading...But I fear that it might be a distraction when you are at the level of taking the first steps into getting ORM.
    ORM is not a new concept. The first time I learned ORM was with EOF using WebObjects about 10 years ago. NHibernate is a pretty solid ORM implementation. If you use it well it will drastically save you time implementing your project.

    ReplyDelete
  14. I have only used xml config with NHibernate, but just started using annotations with Hibernate/Spring. Tons easier and much faster to get done. Maybe someone who has used it with NHibernate/Spring can share. I'd think it would be about the same.

    ReplyDelete
  15. Also, worth a look at is Castle ActiveRecord. It's a wrapper for NHibernate that lets you map all of your entities in code instead of xml and creates the CRUD for you. www.castleproject.org/.../index.html
    Saying that, I don't like it because it intertwines your domain and repository code and I like to keep them separate. But if you're someone that likes to call Customer.Load(customerID), and don't mind extra layers on top of your domain, you should check it out.

    ReplyDelete