Trying out Linq for NHibernate
The hillbilly loves to be humbled. It's why he refers to himself in the third person. It's a liberating feeling knowing there are people out there who are smarter, faster, and know more lyrics to Air Supply songs than you. And I always like to give people that smug feeling of being better than someone, even if it's just me.
Such was the feeling I've ended up at after a foray into Linq to NHibernate. But before we get to the part where I felt stupid, the path that led me to Linq to NHibernate is interesting in a "let's pad this out" kinda way.
I'm building a search screen for an application for land surveyors. In it, you enter a bunch of criteria for various fields, click Search and moan about how it was easier when everything was on paper while you wait for the results to appear.
My initial plan to implement this was to have a search DTO in the client that would collect all the criteria up. That DTO would be converted to a collection of SearchCriteria domain objects which would be passed to the data layer. The data layer would then convert it once more into NHibernate expressions for use with the query. Nice and encapsulated, albeit a little conversion-heavy.
Then I got to thinking about Linq for NHibernate and decided to give it a test drive. The end result was a slightly different approach. Namely, I convert the DTO into a collection of Linq Expressions and pass *that* to the data layer, which could then iterate through each of them and append them to a preset Linq query.
Here's an example of the code to execute the query in the repository.
public IList<T> FindAll<T>( IList<Expression<Func<T, bool>>> criteria ) { var query = from item in CurrentSession.Linq<T>( ) select item; foreach ( var criterion in criteria ) { query.Where( criterion ); } return query.ToList( ); }
A little different from the traditional from x in y select x where moo examples mostly because I don't know what the where clause is going to be at design time. The type for the criteria looks a little intimidating but because of it, I can add criteria to the list in an aesthetically pleasing way. To wit:
searchCriteria.Add( job => job.JobNumber.Contains( suppliedJobNumber ) ); searchCriteria.Add( job => suppliedOffices.Contains( job.Office.Id ) ); searchCriteria.Add( job => job.OrderedData > suppliedData ) );
Here's where things start to go awry. Linq to NHibernate is still very much experimental. For example, the second criteria above doesn't actually work. It assumes that Contains will be called only on strings, not lists. And during my travels, I discovered two other examples that won't work:
searchCriteria.Add( job => "Name1Name2Name3".Contains( job.Name ) ); searchCriteria.Add( job => job.Id == idList[0] ) );
That is, it won't allow an expression to be evaluated as the argument for a Contains call on a string. And you can't use an array index in your comparisons either.
But I did get the code from a branch called "experiments" after all. So rather than complaining, and since I've got the time, why not try to actually implement these? So I started with the one I actually had an immediate need for: getting Contains working on a List.
I started to feel a little nervous about this early on what with the advanced Reflection/Linq concepts involved, not to mention my tenuous grasp of the Visitor pattern. Eventually, I ended up at the line of code that was failing:
return Expression.Lambda(typeof(Func<>).MakeGenericType(expression.Type), expression) .Compile().DynamicInvoke();
I don't doubt that this kind of code is necessary. We are, after all, dealing with a Linq provider that needs to build and parse an expression tree. And I'm pretty sure that with some initial guidance, I'd be reasonably productive in implementing this. (To be fair, looking more closely at that line of code, it's only the MakeGenericType call that baffles me completely. And at the time of writing, I was still too lazy to look it up.)
To put Linq to NHibernate back into context, the reason I like it is because I can essentially do what I originally intended (build a list of criteria and pass it to the data layer) without having to write a bunch of conversion code and without having to create NHibernate expressions in my service layer. As a result, it holds great promise for me but the next couple of days will tell if it's a time/productivity trade-off I can make right now. So if you have some time on your hands, I'll respectfully add my vote for taking a look at Linq to NHibernate.
But don't let it interfere with your banjo lessons...
Kyle the Time-slotted