Archive

Archive for March, 2009

NHibernate Lambda Extentions Gotcha

March 4, 2009 Leave a comment

Recently I came across the open source NHibernate Lambda Extensions project on Google Code.  This small library provides a set of extension to remove the magic strings from your NHibernate criteria queries, and it’s great (once you get used to the conventions).

I’ve been slowly migrating queries across to use NHLambda and tripped up on the following query which is effectively Survey.Status == Complete and Survey.Questionnaire.Id = questionnaireId.


            var expected = DetachedCriteria.For<Survey>()
                .Add( Restrictions.Eq( "Status", SurveyStatus.Complete ) )
                .Add( Restrictions.Eq( "Questionnaire.Id", questionnaireId ) )
                .CreateAlias( "Answers", "answerAlias" )
                .SetProjection( Projections.Property( "answerAlias.Id" ) );

Initially I had converted it as


            Answer answerAlias = null;
            var actual = DetachedCriteria.For<Survey>()
                .Add<Survey>( s => s.Status == SurveyStatus.Complete )
                .Add<Questionnaire>( q => q.Id == questionnaireId )
                .CreateAlias<Survey>( s => s.Answers, () => answerAlias )
                .SetProjection( LambdaProjection.Property( () => answerAlias.Id ) );

which actually generates a query equivalent to Survey.Status == Complete and Survey.Id == questionnaireId.

After kicking myself for being lazy and not writing a test first, I wrote a test and realised that I had implicitly traversed an association in the second restriction.  There are two ways to solve this, either explicitly create the alias, or (as I have just considered and tested while writing this post) correctly place the restriction on the survey and navigate to Questionnaire.Id.  Both approaches are shown below.


            // Explicitly create and navigate the alias
            Questionnaire questionnaireAlias = null;
            Answer answerAlias = null;
            var actual = DetachedCriteria.For<Survey>()
                .Add<Survey>( s => s.Status == SurveyStatus.Complete )
                .CreateAlias<Survey>( s => s.Questionnaire, () => questionnaireAlias )
                .Add( () => questionnaireAlias.Id == questionnaireId )
                .CreateAlias<Survey>( s => s.Answers, () => answerAlias )
                .SetProjection( LambdaProjection.Property( () => answerAlias.Id ) );

            // Apply the restriction to Survey and navigate to Questionnaire.Id
            var actual2 = DetachedCriteria.For<Survey>()
                .Add<Survey>(s => s.Status == SurveyStatus.Complete)
                .Add<Survey>(s => s.Questionnaire.Id == questionnaireId)
                .CreateAlias<Survey>(s => s.Answers, () => answerAlias)
                .SetProjection(LambdaProjection.Property(() => answerAlias.Id));

I don’t know which one of these is more correct (or even if one is better that the other), but they both look a hell of a lot better than the original with it’s many magic strings.

Anyway, the moral of this story is twofold; one, beware implicit association traversals when migrating queries to NHLambda and two, don’t be a clown – write unit tests to cover the migrated queries!  =)

Follow

Get every new post delivered to your Inbox.