views:

1512

answers:

4

What's the best practice for handling exceptions in NHibernate?

I've got a SubjectRepository with the following:

    public void Add(Subject subject)
    {
        using (ISession session = HibernateUtil.CurrentSession)
        using (ITransaction transaction = session.BeginTransaction())
        {

            session.Save(subject);
            transaction.Commit();
        }
    }

And a Unit Test as follows:

        [Test]
    public void TestSaveDuplicate()
    {
        var subject = new Subject
        {
            Code = "En",
            Name = "English"
        };

        _subjectRepository.Add(subject);

        var duplicateSubject = new Subject
        {
            Code = "En",
            Name = "English1"
        };

        _subjectRepository.Add(duplicateSubject);
    }

I got to the point of handling the error generated by the unit test and got a bit stuck. This fails as expected, though with a GenericADOException, I was expecting a ConstraintViolationException or something similar (there is a uniqueness constraint on the subject code at database level).

The ADOException wraps a MySQL Exception that has a sensible error message but I don't want to start breaking encapsulation by just throwing the inner exception. Particularly as MySQL isn't finalised as the back end for this project.

Ideally I'd like to be able to catch the exception and return a sensible error to the user at this point. Are there any documented best practice approaches to handling NHibernate Exceptions and reporting back up to the user what went wrong and why?

Thanks,

Matt

A: 

The general question is going to be, what do you want to tell the user, and who is the user?

If the user will sometimes be another computer (i.e., this is a web service), then you would want to use the appropriate mechanism to return a SOAP Fault or HTTP error.

If the user will sometimes be a UI of some sort, then you may want to display a message to the user, but what would you tell the user so he can do something about it? For instance, most web sites will say, "sorry, we had an unexpected error", no matter what the reason. That's because there's usually nothing the user could do about the error.

But in either case, the choice of how to tell "the user" is a matter for the Presentation layer (UI tier), not for the DAL. You should possibly wrap exceptions from the DAL in another exception type, but only if you're going to change the message. You don't need your own exception class, unless your callers would do something different if it's a data access exception rather than some other kind.

John Saunders
+2  A: 

I would handle it in the Add method as such:

public void Add(Subject subject)
{
    using (ISession session = HibernateUtil.CurrentSession)
    using (ITransaction transaction = session.BeginTransaction())
    {
        try
        {
            session.Save(subject);
            transaction.Commit();
        }
        catch (Exception ex)
        {
            transaction.Rollback();
            // log exception
            throw;
        }
    }
}

In the catch block, you should first rollback the transaction and log the exception. Then your options are:

  1. Rethrow the same exception, which is what my version does
  2. Wrap it in your own exception and throw that
  3. Swallow the exception by doing nothing, which is very rarely a good idea

You don't have any real options for handling the exception in this method. Assuming that the UI calls this method, it should call it in its own try..catch and handle it by displaying a meaningful error message to the user. You can make your unit test pass by using the ExpectedException(type) attribute.

To answer your question directly, you should create your own "sensible error" by extending Exception and throw that with the original exception as its InnerException. That's the exception wrapping technique I listed in (2).

Jamie Ide
I think wrapping the exception is the best option as well.The inner exception is something along the lines of MySQLException Message = "Duplicate entry 'En' for key 3".As this will be called directly by the front end I'd like to return a meaningful message.
A: 

All the Nhibernate exceptions are non recoverable, you could revisit the design of the app/data layer if you are trying to recover from nhibernate exceptions . You can also Take a look at spring.net 's exception translation implementaion Also you manually handling transactions on exceptions is tedious and error prone, take a look at nhibernate's contextual sessions . Spring.net also has some nice helpers around nhibernate .

Surya
+1  A: 

I'd probably validate the input before saving the object; that way you can implement whatever validation you like (e.g. check the length of the Subject Code as well as the fact that there aren't any duplicates), and pass back meaningful validation errors back to the user.

The logic is as follows; exceptions are used to indicate exceptional circumstances that your program doesn't cater for. A user entering a duplicate Subject Code in your example above is something your program should be catering for; so, rather than handling an exception because a DB constraint gets violated (which is an exceptional event and shouldn't be occurring), you'd want to handle that scenario first and only attempt to save the data when you know that the data you're saving is correct.

The advantage with implementing all validation rules in your DAL is that you can then ensure that the data going into your DB is valid as per your business processes in a consistent manner, rather than relying on the constraints in your DB to catch those for you.

zcrar70