|
LATEST POSTS
Friday, December 18, 2009
Gonna delve away from CodeBetter tradition into more of the realm of our sister site, Devlicious. I.e. This post will be about code, not about thinking about writing code. Having gone through this monstrous exercise though, I gotta say, Ivory Tower Architect is looking like a mighty fine role these days. I’m working on a search interface that uses NHibernate to search for people using various criteria, such as Languages Spoken (generally a zero to many relation), Special Skillz, and Preferred Side Dish for Various Roadkill. It uses HBM files and a couple of the collections on the Person object are mapped as composite-elements, like so: <class name="Person" table="People">
...
<bag name="SpecialSkills" table="Mad_Skills">
<key column="PersonFK" />
<many-to-many class="Skill" column="SkillFK" />
</bag>
...
<bag name="PersonalHygieneMilestonesReached" table="Personal_Hygiene">
<key column="PersonFK" />
<composite-element class="PersonalHygiene">
<many-to-one name="HygieneAction" column="HygieneFK" />
<property name="LastPerformed" column="Date_Last_Performed"/>
</composite-element>
</bag>
...
</class>
This is just a representative sample. The Person class has a collection of Skill objects (accessed via a property named SpecialSkills) as well as a collection of PersonalHygiene objects (mapped via the PersonalHygieneMilestonesReached property). The PersonalHygiene object contains a HygieneAction object (which is just an ID and Description) and a DateLastPerformed property.
In the search interface, we'd like to search for people who have, say, bathed. (I'm ignoring the date last performed to avoid muddying the wa--err...clouding the issu---ummm, complicating things.) So the search interface gives us a dropdown list, the user selects “bathed”, and our NHibernate-based data access class goes on its merry way.
public IList<Person> FindByPersonalHygiene( int PersonalHygieneId )
{
var query = DetachedCriteria.For<Person>( )
.CreateAlias( "PersonalHygieneMilestonesReached", "hygiene" )
.Add( Property.ForName( "hygiene.Id" ).Eq( PersonalHygieneId );
return query.GetExecutableCriteria( Session ).List<Person>( );
}
Similarly, here's how we could find everyone who has a given special skill, say, "Converses at a Third Grade Level.":
public IList<Person> FindBySpecialSkill( int SpecialSkillId )
{
var query = DetachedCriteria.For<Person>( )
.CreateAlias( "SpecialSkills", "skill" )
.Add( Property.ForName( "skill.Id" ).Eq( SpecialSkillId );
return query.GetExecutableCriteria( Session ).List<Person>( );
}
All well and good and does what we want. It’s slightly different than searching on a property of the Person object in that we are searching for the existing of an item in one of its collections. Hence, the use of CreateAlias.
The next task: Add the ability to apply multiple filters to our list of people. If someone matches ANY of the supplied criteria, they are returned by the search. That is, we want to OR the criteria together.
Let's search for anyone that has bathed OR has the ConversesAtAThirdGradeLevel special skill. Luckily, there is an Expression.Or method that, in theory, should make this easy. Let's take a look at this special case before generalizing some.
public IList<Person> FindByHygieneOrSkill( int PersonalHygieneId, int SpecialSkillId )
{
var query = DetachedCriteria.For<Person>( )
.CreateAlias( "PersonalHygieneMilestonesReached", "hygiene" )
.CreateAlias( "SpecialSkills", "skill" )
.Add( Expression.Or(
Property.ForName( "hygiene.Id" ).Eq( PersonalHygieneId ), Property.ForName( "skill.Id" ).Eq( SpecialSkillId )
);
return query.GetExecutableCriteria( Session ).List<Person>( );
}
This is essentially a combination of the two queries. We need to add all the aliases up front because Expression.Or takes two ICriterion objects, not two ICriteria objects. This can be generalized as well by modifying the FindByXXX methods so that they look more like this:
public ICriterion AddAliasAndGetCriteriaForSpecialSkill( DetachedCriteria query, int SpecialSkillId )
{
query.CreateAlias( "SpecialSkills", "skill" );
return Property.ForName( "skill.Id" ).Eq( SpecialSkillId );
}
public IList<Person> GetPeopleMatching( SearchCriteria criteria )
{
var query = DetachedCriteria.For<Person>( );
var disjunction = new Disjunction( );
if ( criteria.SpecialSkillId.HasValue )
{
var criterion = AddAliasAndGetCriteriaForSpecialSkill( query, criteria.SpecialSkillId.Value );
disjunction.Add( criterion );
}
if ( criteria.PersonalHygieneId.HasValue )
{
var criterion = AddAliasAndGetCriteriaForPersonalHygiene( query, criteria.PersonalHygiene.Value );
disjunction.Add( criterion );
}
query.Add( disjunction );
return query.GetExecutableCriteria( Session ).List<Person>( );
}
Kinda messy with all the if statements and the specialized AddAliasAndGetCriteriaForXXX methods but those can be cleaned up. I switched to using a Disjunction because it allows you to chain a bunch of ICriterion objects together, rather than limiting it to two like Expression.Or does.
The reason I won't show you the cleaned up code because when all is said and done, this won't actually work. If you look at the underlying query being executed, it looks something like this:
SELECT * -- Not really * but you get my drift
FROM Person p
INNER JOIN Person_SpecialSkills pss ON p.Id = pss.PersonId
INNER JOIN Person_PersonalHygiene pph ON p.Id = pph.PersonId
WHERE pss.SkillId = @p0 OR pph.PersonalHygieneId = @p1
On the surface, this looked correct to me intuitively. It gave me results and those people had the specified skill and/or hygiene habit.
But I noticed there were people missing in the results. Specifically, people that had either no special skills or no personal hygiene habits. Which is when the INNER JOIN assignment from my second year databases class came rushing back to me. Of course, if you use INNER JOIN, you exclude any data where there is no match between the two tables. So my little SQL statement above will include only people who have BOTH a special skill AND personal hygiene and only those that match the given criteria.
Instead, I could use OUTER JOIN. But I didn’t much like how things were starting to meld together with the aliases and the criteria so I decided on a separate approach. In essence, for each search filter, I want to run a query. Then I want to return the list of people from all those queries. I could do that programmatically, I suppose. I.e. Execute the queries, then use List.Intersect to pull them all together.
But that ain’t how the hillbilly rolls. Instead I went with subqueries. The underlying pseudo-SQL:
SELECT *
FROM Person
WHERE Id IN ( SELECT PersonId FROM Person_SpecialSkills WHERE SkillId = @p0 )
OR Id IN ( SELECT PersonId FROM Person_PersonalHygiene WHERE PersonalHygieneId = @p1 )
To achieve this, I need slight modification to my original method
public DetachedCriteria FindByPersonalHygiene( int PersonalHygieneId )
{
var query = DetachedCriteria.For<Person>( )
.SetProjection( Projections.Property( "Id" ) )
.CreateAlias( "PersonalHygieneMilestonesReached", "hygiene" )
.Add( Property.ForName( "hygiene.Id" ).Eq( PersonalHygieneId );
return query;
}
The changes: return the actual query and specify a projection, which is fancy-Hibernate-speak for "what fields to I want this query to return?".
From here, I created the main search method:
In this case, finders is a dictionary of Func<int, DetachedCriteria> objects keyed on a SearchType enum. It's initialized elsewhere. I use it as a sort of pseudo-strategy-type thing and if you claim that it's not *really* a strategy pattern, I won't disagree because I don't care. I just know it's cleaner than using a switch statement.
public IList<Person> FindBy( IList<SearchExpression> expressions )
{
var query = Session.CreateCriteria( typeof (Person) );
var disjunction = new Disjunction( );
foreach ( var expression in expressions )
{
var subquery = finders[expression.SearchType]( expression.Id );
disjunction.Add( Subqueries.PropertyIn( "Id", subquery ) );
}
query.Add( disjunction );
return query.List<Person>( );
}
The main method is pretty clean now. It loops through the list of all the filters the user has selected and adds an appropriate subquery, checking to see if the Person.Id is in the returned query.
Now some of you may cry foul in the name of performance or some other NHibernate feature that makes all this obsolete. To you, I say, software is a journey, not a destination.
Kyle the Tacit
Monday, July 27, 2009
For those of you skimming your RSS reader, I'll help you filter because I suspect my target audience is narrow with this one. This post deals with using the Fluent NHibernate IPersistenceConfigurer with Sharp Architecture. The hillbilly is a-codin’ agin! And back with Sharp Architecture which I had all but abandoned in favour of Repeaters and ItemCommands and various other monsters used to frighten young ASP.NET developers. (“Eat all your vegetables, Johnny, or tonight, when you’re fast asleep, THE VIEWSTATE OGRE WILL EXPAND INSIDE YOUR CLOSET UNTIL IT BLOWS UP ALL YOUR BROWSERS!!!”). Ah, it’s good to back in the realm of geek jokes that will surely embarrass my children at some future date… Much has happened in the Sharp Architecture world since I last visited. And to Fluent NHibernate, of which Sharp takes much advantage. It is now possible to work with NHibernate entirely without XML through the magic of fluent interfaces. Something that appeals to me aesthetically though it’s not a position I’ll defend to the death, or even to the slightly bruised. Typically, NHibernate can be configured as follows: var config = new Configuration( );
config.Configure( pathToNHConfigFile );
var sessionFactory = config.BuildSessionFactory();
Note that the parameter to Configure is optional. It appears that if one isn’t provided, it will default to looking for a file called NHibernate.config.xml. Though judging by how quickly things are changing, that may not be true by the time I click Publish.
The alternative with Fluent NHibernate is as follows:
var config = Fluently.Configure( )
.Database( MsSqlConfiguration.MsSql2005
.ConnectionString( c => c.FromConnectionStringWithKey("mainConnection") )
.ShowSql( )
.ProxyFactoryFactory( "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle" )
);
var sessionFactory = config.BuildSessionFactory( );
In this case, you don’t even need an NHibernate Configuration object. Fluent NH looks to be doing all the work behind the scenes. All in all, it’s pretty clean and clear, save for that ProxyFactoryFactory call which may make sense to someone who has been actually following NH development. Personally, it required much BinGoogling to figure out that I even needed it.
It’s possible to get lost in the options though. Fluently.Configure( ) takes an optional NHibernate Configuration object, which presumably could be configured via a config file. And you can add mappings to the NHibernate Configuration *and* the Fluent NH Configuration. It’s not mind-bogglingly circular but it does add an element of fun when you’re prone to just bang away without putting any thought into it.
This is where I segue back to SharpArchitecture. Sharp has supported Fluent NH for a while now with the class mappings. It also provides a nice interface for easily configuring NHibernate through some overloaded Init methods. But none of them supported the Fluent NH configuration API, likely because it’s relatively new.
So at some point, another overload was created that took a Fluent NH IPersistenceConfigurer object, which contains the details about the NH configuration (e.g., the MsSqlConfiguration object above). However, it doesn’t account for situations where you provide an IPersistenceConfigurer but no config file. That is, the config file was still required even though you don’t need it.
A patch is under evaluation to fix this so that something like the following will soon be possible:
var configuration = MsSqlConfiguration.MsSql2005
.AdoNetBatchSize(500)
.ShowSql()
.ConnectionString(c => c.FromConnectionStringWithKey("main"))
.ProxyFactoryFactory("NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");
NHibernateSession.Init(webSessionStorage,
new string[] { Server.MapPath("~/bin/TingsForSale.Data.dll") },
new AutoPersistenceModelGenerator().Generate(),
null, null, null, configuration);
Until then, the only way to use the IPersistenceConfigurer aspect of Sharp Architecture is to either: a) provide a dummy config file, or b) mimic the code in NHibernateSession.Init like so:
var configuration = MsSqlConfiguration.MsSql2005
.AdoNetBatchSize(500)
.ShowSql()
.ConnectionString(c => c.FromConnectionStringWithKey("main"))
.ProxyFactoryFactory("NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");
var fluentConfiguration = Fluently.Configure()
.Database(configuration)
.Mappings(m =>
{
m.AutoMappings.Add(new AutoPersistenceModelGenerator().Generate());
m.FluentMappings.AddFromAssemblyOf();
m.HbmMappings.AddFromAssemblyOf();
});
var nhConfiguration = fluentConfiguration.BuildConfiguration();
var validationEngine = new ValidatorEngine();
validationEngine.Configure();
ValidatorInitializer.Initialize(nhConfiguration, validationEngine);
var sessionFactory = nhConfiguration.BuildSessionFactory();
NHibernateSession.SessionFactories.Add(
webSessionStorage.FactoryKey,
sessionFactory);
NHibernateSession.Storages.Add(webSessionStorage.FactoryKey, webSessionStorage);
I'm hoping we can clean up the NHibernateSession.Init method somewhat because calling it with all those nulls looks mighty sketchy. But since Sharp Architecture recently came out with an official 1.0 version, I'm hesitant to start mucking with public interfaces.
Kyle the Private
Tuesday, January 06, 2009
I've said this before and I'll say it again: Lord Tunderin' Jayzus! The target this time is NHibernate Profiler. As of this moment, I've had it "installed" for all of fifteen minutes and I've already reduced the query count of one page from 205 to 1. Such is the story you will read about today. I suspect typing it out will take longer than it took me to optimize the page I'm about to describe. By the way, I put "installed" in quotes because installing the application consists of unzipping it to a folder of your choosing. Means you need to set up shortcuts on your own but I like the minimalist approach myself. The app I've already described. To sum up, Surveyors have many MonumentAssignments. Here is the (original) Fluent NHibernate mapping for review: public SurveyorMap() {
WithTable("Surveyor");
Id(x => x.ID, "SurveyorID")
.WithUnsavedValue(0)
.GeneratedBy.Identity();
// Boring stuff
HasMany( x => x.Monuments )
.Cascade.AllDeleteOrphan( )
.WithKeyColumn( "SurveyorID" );
}
Notice how I put (original) in brackets there. That's what we in the legitimate journalism field like to call "foreshadowing" (or, more likely, foreboding).
The page in question provides a list of Surveyors. First name, last name, commission number. That's it. It isn't displaying anything from the Monuments collection. (See? More foreshadowing.)
So it was much to my surprise when I profiled this application that it outlined a total of 205 SQL statements for 202 Surveyors. "Odd" says I, and I proceeded to work my way through NHibernate Profiler to see what it can tell me.
Quite a bit as it turns out. The screenshot at right shows a summary of the alerts. The one that jumped out at me was 199 SELECT N+1 alerts.
The ellipsis took me to this page which was pretty helpful. It says if I'm going to iterate over the collection, I should consider eager loading. Well, you won't accuse me of not being eager so that's what I did. My GetAll method now looked like this:
public IList GetAll()
{
var criteria = DetachedCriteria.For( );
criteria.SetFetchMode( "Monuments", FetchMode.Eager );
return criteria.GetExecutableCriteria( Session ).List( );
}
That baby reduced my query count from 205 to 3. And my alerts consisted of two "Use of implicit transactions discouraged" and one "Unbounded result set". "Acceptable" says I!
But then something occurred to me. I'm not using the Monuments collection in this page. Why is it even retrieving them? Maybe I *am* being too eager after all...
As it turns out, the HasMany call in my map doesn't retrieve the Monuments lazily by default as I had expected it would. So with a flourish of my hand, I remedy that:
public SurveyorMap() {
WithTable("Surveyor");
Id(x => x.ID, "SurveyorID")
.WithUnsavedValue(0)
.GeneratedBy.Identity();
// Boring stuff
HasMany( x => x.Monuments )
.Cascade.AllDeleteOrphan( )
.WithKeyColumn( "SurveyorID" )
.LazyLoad();
}
I also reverted my previous eager change because it was no longer necessary and poof! I am now down to a single SQL query for the page, which is as it should be. I still prefers that I limit my result set and put things in an explicit transaction so I'll be tackling those shortly. But for now, I'm agog, AGOG I TELL YOU!!1!
Granted, exposing my ignorance is not exactly an Olympic event. But just this morning, I made some facetious remark (no, it's true) that I wanted to make an application that was wildly successful in spite of itself. Several people responded with variations of "is there any other kind?" Given what I know of its creator, I'm hoping this is an exception: an application that is wildly successful because it fills a niche *and* because it is reliable and well-built.
That said, Ayende, if you're reading, it'd be nice if you could clear individual sessions from the list...
Kyle the Reduced
Wednesday, December 24, 2008
I'm such a nice brother. For Christmas this year, I got my older brother an MVC application to help him with some of his community work for his land surveying responsibilities. In return, I've got a nice little app that's got at least two more posts in it and probably just as many Dimecasts. I'll describe the domain that led to this post so's I don't have to change any of the sample code I'll inevitably paste in here. We have land surveyors. Each one, over the course of his or her career, will use "monuments". Which as far as I can tell are those big sticks with orange paint on the end you often see stuck into the ground. Why are they called something as grandiose as monuments? My brother likes to say it's the second oldest profession in the world (and given what some of them charge, they aren't far removed from the first). So in ye olde dayes, instead of using sticks, they'd erect monuments. See, the Pyramids and the Eiffel Tower aren't actually the grand constructions you think they are. They're just the result of diligent land surveyors trying to find the property line. Anyway, I'm getting off track (though to my credit, I took longer than usual to do so this time). A surveyor will use monuments/sticks and they each one has an official label. These days, the label appears to be related to the company you work for. So all surveyors currently working for Tango & Cash Geomatics will use monuments labelled TC. If a surveyor jumps ship over to Rowan & Martin Land Surveyors, he'll then use a monument labelled RM. And so on and so forth. Here are the tables involved: A pretty standard many-to-many relationship. But where this differs from almost all of the NHibernate examples you see is that there is data attached to the relationship. I.e. it's not enough that we know Sandy McLean used monument TH, we want to know the period in which he and/or she used it. Now, I'm using Fluent NHibernate here because, well, it's my app. I don't to answer to you people. And the question arose, how do I map this to my domain? I've skipped an obvious precursor question though. Namely: what does the domain look like. This may not be crazy obvious at first glance. Of course, there is a Surveyor object and a Monument object (sparse as it is at the moment). And you might think that a surveyor has a collection of Monuments but that's not the case. In fact, as was pointed out to me by a few people (acknowledgements are at the end), the relationship 'twixt Surveyors and Monuments is a domain concept in its own right. That is, we need another object to represent that relationship. A MonumentAssignment, for example, which contains the properties you might expect: Monument, Surveyor, YearStarted, and YearEnded. Mapping the MonumentAssignment with Fluent NHibernate was relatively easy ("relatively" being the key word; this *is* NHibernate we're dealing with): public class MonumentAssignmentMap : ClassMap
{
public MonumentAssignmentMap( )
{
WithTable( "SurveyorMonument" );
Id( x => x.ID, "SurveyorMonumentID" );
References( x => x.Surveyor, "SurveyorID" )
.WithForeignKey( "SurveyorID" )
.FetchType.Join( );
References( x => x.Monument, "MonumentID" )
.WithForeignKey( "MonumentID" )
.FetchType.Join( );
Map( x => x.YearStarted );
Map( x => x.YearEnded );
}
}
The References calls required some Googling. They basically allow you to map a class to multiple tables.
Now to the Surveyor map. At first, I thought this required the use of HasManyToMany because, y'know, this is a many-to-many relationship. That's not quite true. Yes, in one sense, a Surveyor has many Monuments and a Monument can have many Surveyors but in fact, a Surveyor isn't directly related to Monument. It is related to MonumentAssignments. In a many-to-many relationship, this intermediate object is usually left out because it doesn't have any meaning other than to relate two objects. In our case, it has a temporal meaning outside of just the relationship itself.
So now, our Surveyor has a many-to-one relationship with MonumentAssignment. Monument, in turn, also has a many-to-one relationship with MonumentAssignment. With that in mind, we can map our Surveyor class thusly:
public class SurveyorMap : ClassMap
{
public SurveyorMap() {
WithTable("Surveyor");
Id(x => x.ID, "SurveyorID");
Map(x => x.CommissionNumber);
Map(x => x.Surname);
Map(x => x.GivenNames);
Map(x => x.YearCommissionGranted);
HasMany( x => x.Monuments )
.Cascade.AllDeleteOrphan( )
.WithKeyColumn( "SurveyorID" );
}
}
That's it. In the end, it wasn't nearly as complicated as it first seemed. Of course, two key ideas led to this (again, relative) simplicity:
- MonumentAssignment is a domain concept in its own right
- Surveyor has a many-to-one relationship with MonumentAssignment, *not* a many-to-many relationship with Monument
This post was brought to you by Colin Jack, Chad Myers, Shane Courtrille, James Gregory and the number 12.
Merry Christmas!
Kyle the Monumental
Monday, October 27, 2008
I'm in the early stages of a new project with the dashing and debonair Brian Donahue. On the plus side, despite his misgivings, the codebase is in pretty phenomenal shape all things considered. SVN repository, automated build and tests, ActiveRecord, views reasonably separated into MVP. Even a half-finished CI process which is impressive for a one-man team. Like any codebase, it has its share of issues but all in all, I won't be using this as a sample app for a brownfield application. On the minus side, he reads my blog so I'll have to be more subtle in the digs I make toward my workplace. Anyway, one of the first things I like to do when I start looking through a steaming pile of excrement that someone calls "codebase" is check out the warnings that are lingering. The code had its share with the vast majority related to an upgrade to the latest version of Rhino Mocks but without changing CreateMock. This was an easy fix as a global search and replace from CreateMock to StrictMock got rid of over 80% of the errors. The rest were a flurry of ReSharper refactorings until I got to the final four. Four assemblies reported the error: found conflicts between different versions of the same dependent assembly. Double-clicking this error gives a helpful message offering to add a binding section to the assemblies app.config file. I say helpful seriously there because, while I had no intention of keeping that section in the app.config, at least it was able to tell me what the error message couldn't. Namely, which assembly was causing conflicts. Or so I thought. The new app.config section seemed to indicate that there were NHibernate conflicts among various projects when I knew for a fact that we had exactly one copy of the NHibernate dll in the entire project. Here's where Reflector comes in. More specifically, here's where it's extensibility comes in. Through the use of the Assembly Graph add-in, I was able to generate a picture of the dependencies. (Click on the image to the right for a bigger version.) As you can see, all referenes to NHibernate are going to the same assembly. But as you can also see, there are three semi-related assemblies that aren't being referenced at all. Which means they can safely be removed from the various projects, yesno? You probably think that's foreshadowing's cooler cousin, foreboding, but it's not. The answer is yes, I was able to remove them with no ill effects. The reason I know there are no ill effects? Unit tests, baby! None o' this "let's take a stroll through the application and see if it still works" nonsense for me*. Remove the reference, run through the build process and watch the magic happen. The reason this is important: I'm pretty sure this add-in doesn't account for dynamically loaded assemblies. And while I'm also pretty sure we would have no reason to dynamically load some peripheral NHibernate assemblies, it's nice to have a suite of unit tests backing up that assertion. So to re-cap: - Unit tests good
- Automated builds good
- Reflector good
- Assembly Graph add-in good
- Subversion good (I haven't provided explicit evidence of this here but it's worth mentioning.)
Kyle the Reflectively Assembled *Brian, if you're reading, I'm lying. I did actually go through each section and make sure it's working. But back me up if anyone asks.
Saturday, September 27, 2008
When presenting on ASP.NET MVC, I use an application I started nigh on two years ago when I was a wee whelp extolling the virtues of Atlas. It's no longer AJAX-y but it does demonstrate MVC and I've put it online at Google Code mostly because I've been promising I would for many months. It's a music catalogue that will extract metadata from all WMA and MP3 files in a folder of your choice and store it in a database. There are some simple controller actions used to query the catalogue in various ways. Check out the ReadMe.txt file for some tips on getting started. Note that you can't just download the code and open it up in Visual Studio. You need to run the build file at least once. There is a batch file that will make this easier the first time (clicktobuild.bat) but bear in mind that this *will* drop and re-create your database. And before you do that, you'll need to create a local-properties.xml file in the confi--...actually, just read the ReadMe.txt. There is a unit testing project in there that I added after the Edmonton presentation. The Edmug guys have whipped their city into shape there because when I said MVC is conducive to TDD, they called me out and said "Prove it, Plaid Man". Hardly complete coverage by any stretch but hopefully demonstrative enough. If you're semantically inclined, this does indeed mean I built the app without using TDD. There's actually a very good reason for that. You see, a plague of locusts appeared when I was first building it and they told me in locust-ese that if I used TDD, they would become a blight upon the land. I'm sure you see my dilemma. Final note on the name. Suvius is just a code name my brothers and I use and I've always liked the sound of it. Flamingo is the national bird of the Bahamas. How do they relate to an online music catalogue application? They don't. I've got a rant I've all but memorized on people's fixation on names but the short version of it is: Amazon, Sun, Java, Oracle, eBay, Facebook, Windsor, and Hibernate. I'd tell you to enjoy but that implies some sort of emotional attachment to the project. Kyle the Flaming-o
Tuesday, April 15, 2008
Here's the thing with NHibernate: It's hard. I'm mildly, bordering on moderately familiar with it and while it's a powerful product, it's a complicated one. This is due in no small part to the fragmented nature of the documentation. Some is on the official site, some in discussion groups, some in FAQs, and some in books. It's getting better but it is still a very daunting and frustrating task to do seemingly simple things when you aren't a master. We'll look at the example that you knew was coming: Select all Jobs that have at least one Location with a Section = 15. Here is the SQL: SELECT *
FROM Job
WHERE JobID IN
( SELECT JobID
FROM Location
WHERE Section = 15 )
Not too bad thanks to years of SQL that have been ingrained at the expense of remembering vital statistics and, on occasion, basic motor skills.
Now before I post a solution, I should point out that one of the reasons NHibernate is hard to figure out is that the domain does not lend itself well to Google. Take this example. I want to convert this SQL to ICriteria format. Searches for various combinations of NHibernate, Subqueries, Collections, and Children do not make one reach for the "I'm feeling lucky" link. I did find this somewhat helpful link until I discovered that the whole thing could be replaced with an Expression.IsNotEmpty criteria.
I'll show *a* solution to my problem:
var subquery = DetachedCriteria.For<job>( );
subquery
.SetProjection( Projections.Property( "Id" ) )
.CreateCriteria( "Locations" )
.Add( Property.ForName( "Section" ).Eq( "15" ) );
var query = DetachedCriteria.For<Job>( );
query
.Add( Subqueries.PropertyIn( "Id", subquery ) );
I'm going to venture an opinion that this is *not* an intuitive piece of code when, in the back of your mind, you're trying to map this to what you know of SQL.
Here is what is going on as I see it. The subquery is equivalent to the following:
SELECT JobID
FROM Job j
INNER JOIN Location l
ON j.JobID = l.JobID
WHERE l.Section = 15
This is different than the subquery above and there is a reason for that, which I think I'll get to. But first, here are some specific areas of confusion:
- SetProjection is essentially saying "what fields do you want to return?" If you ignore it, it will return whatever object the query is for (in this case, a list of Jobs).
- CreateCriteria( "Locations" ) is used for the INNER JOIN on Job. The Add will create a criteria for the Location (which is why I indented it further).
The reason for my confusion is the disconnect between the terms when mapping them to what I know of SQL. Could be my cursory knowledge of the underlying theory. Could be that it's just hard coming up with names for these things.
Now we get to the reason why the subquery is on Job and not Location. In my domain model, Job contains a collection of Location objects. The Location objects do not have a reference back to the parent, nor do they have a JobId property. There is a JobId column for the table in the database but that's used only by NHibernate to populate the collection of Locations for a particular Job.
If I were to create the subquery on Location, I have nothing to return in the Projection. There is no JobID property on the Location. And NHibernate deals with objects in its queries (mostly), not database schema. So I needed to create an INNER JOIN subquery solely so that I could return the ID property on a Job object.
Once that is finished, we create the main query, again on Job. The sole point of confusion I had here was that I saw In in the list of methods on Subqueries and tried to use that instead of PropertyIn.
It took an inordinate amount of time to get to this point compared to how much time I spend with other tools. Like I said, maybe it's the problem domain. Maybe it's the way my brain is wired for this type of problem.
The underlying point is...well, I'm not all too sure actually. Not sure what the recommendation is here. I'm not knocking NHibernate because the fact is, it does make my life easier for the 90% of the time I'm not doing stuff like this. The point isn't really to stick with it either, though again, I think you should if you've tried it and dismissed it for being too complicated. Guess I just wanted to say I worked really hard today.
Concluding note: I'm aware of the existence of HQL. I'm building a search screen and the prospect of converting an arbitrary number of search parameters into HQL didn't appeal to me.
Kyle the Unrelenting
Wednesday, April 09, 2008
What a difference a day makes. Spent the better part of yesterday trying to figure out the Linq to NHibernate so that I could use Contains on something other than a string, like so: from job in session.Linq( )
where officeIds.Contains( job.Office.Id )
select job;
The book Linq in Action, as well as Matt Warren's series on query providers, both provided a lot of help in understanding the code. As I started sifting through it and looking at failing tests, I started making some fairly sweeping changes to the code. Not necessarily to check it in, mind you, more for my own eddy-ficashun. I was in spike mode, not "someone else may actually look at this" mode.
In the end, it wasn't quite as hard as I first thought. 'Course, I have a built-in advantage since parsing expression trees is much like parsing the family tree, what with all the AndAlso's and OrElse's. ExclusiveOr was a new concept though.
By the time I was done, I had an ExpressionVisitor very similar to the one Matt Warren specified and I had implemented it in a couple of the visitors in the codebase. It still didn't do what I wanted but I was a lot clearer on how things flowed. It fixed a couple of bugs while introducing others and while I wasn't any further ahead in my specific task, it was a nice break to do something I haven't in a long time.
Then I updated to the latest and lo! Chadly (who I'd link to if I had a blog for him) has implemented what appears to be the same thing, albeit in a much cleaner way. And even better! Doug Mayer (also conspicuously unlinked) has implemented the exact Contains support that I needed! Well, almost. Contains works on arrays now but not on Lists (or anything deriving from ICollection methinks). But ToArray helps out there.
Anyway, here I am a day later having learned about query providers and my work still got done for me in my absence. All hail OSS!
Kyle the NHiberLinq'd
Monday, April 07, 2008
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
Thursday, March 20, 2008
Normally when I have a problem I can't solve right away, I bang away at it until I have a half-assed solution that I can post here so that others can provide a better solution in the comments. It's how I've found about four methods for testing RedirectToAction, got tips for automating my deployments, and figured out the ConventionController. Quite frankly, I post here so that someone else can show me a better way to do things 'cause I've learned long ago my way ain't it. This time, however, I've searched and experimented and have come up empty. So there will be no facade that I know what I'm doing this time. I'll just plain admit that I can't get it to work. I suppose I could bitch and complain about how hard this is. That seems to be a common theme I've seen in many other places when someone can't figure something out. Whine about how it should be easier, then have some kid in tenth grade post a simple yet elegant solution in the comments within the first five minutes. Alas, I don't have the pride to assume that it's someone else's fault I can't get my work done. 'Spose I should get to the problem at hand lest I lose potential problem-solvers. I have a domain object called Job. It contains a collection of Location objects. A Location is an abstract class with two implementations: RuralLocation and UrbanLocation. The heart of my problem is: How do I represent this in an NHibernate mapping? As defined in the documentation, I'm using a "table-per-concrete-class" strategy. I'll listen to arguments that it's not appropriate but consider this: Locations are value objects. They have no identity in and of themselves. A sample location would be NE-15-1-29-1W, which is a rural location (Northeast quarter section, section 15, township 1, range 29, 1 west of the prime meridian). There is no benefit to storing this as an individual entity in the database and attaching it to many jobs. Locations are essentially bandied around like money when you listen to the clients talk. The reason I'm not considering a "table-per-class" or "table-per-subclass" hierarchy is because the two classes have exactly zero fields in common. So in the former, we'd have a table where half the fields would be null in every single row. And in the latter, we'd have a Location table containing two fields, an ID and a LocationType, and nothing else. Maybe having such a table isn't too bad but it sure feels like I'm altering the schema for the sake of NHibernate. So for better or for worse, I have a "table-per-concrete-class". This is easy enough to represent in mapping files for each class individually as per the NHibernate documentation. Where I'm stuck is how to link these concrete classes back to the Job mapping file. Back when I had only the one location type, the mapping looked so: <bag name="Locations" cascade="all-delete-orphan"> <key column="JobID" /> <one-to-many class="Trilogy.Gunton.Model.RuralLocation" /> </bag>
Fairly straightforward and pretty much right out of the docs. But now, I need to map to an abstract Location class that has no representation in NHibernate. The docs mention the <any> element and Ayende has something similar but for the life of this hillbilly, the answer still eludes.
So to my insightful and, I pray, generous readers: <ahem>....help!
### UPDATE ###
Sorry folks, forgot to include relevant diagrams. Database diagram and class diagram are included below. The Locations property on Job is an IList<Location>. Click for larger versions:
Kyle the Unassisted
Saturday, March 15, 2008
I'll keep this one fairly Google-able due to the amount of time it took me to figure it out sans a similar reference. Seasoned NHibernate veterans will likely guess the issue before they finish reading this sentence. Here's the error message I was getting from NHibernate: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary. at System.ThrowHelper.ThrowKeyNotFoundException() at System.Collections.Generic.Dictionary`2.get_Item(TKey key) at NHibernate.Cfg.XmlHbmBinding.CollectionBinder.BindCollectionSecondPass(XmlNode node, Collection model, IDictionary`2 persistentClasses) at NHibernate.Cfg.XmlHbmBinding.CollectionBinder.<>c__DisplayClassd.<AddCollectionSecondPass>b__c(IDictionary`2 persistentClasses) at NHibernate.Cfg.Configuration.SecondPassCompile() at NHibernate.Cfg.Configuration.BuildSessionFactory() at Trilogy.Gunton.DataAccess.SessionBuilder.GetSessionFactory(Database selectedDatabase) in SessionBuilder.cs: line 72 at Trilogy.Gunton.DataAccess.SessionBuilder.GetSession(Database selectedDatabase) in SessionBuilder.cs: line 21 at Trilogy.Gunton.DataAccess.UnitOfWorkFactory.Create() in UnitOfWorkFactory.cs: line 19 at Trilogy.Gunton.DataAccess.RepositoryBase`1.GetAll() in RepositoryBase.cs: line 19 at Trilogy.Gunton.Tests.Integration.JobTypeRepositoryFixture.Should_retrieve_job_types() in JobTypeRepositoryFixture.cs: line 16
This started happening after I added a collection to my object (which Karl Seguin's post was instrumental in getting set up). Here's what I added to the <class> element in the .hbm.xml file for the object (fingers crossed on the XML formatting): <bag name="Locations" generic="true">
<key column="JobID" />
<one-to-many class="RuralLocation" />
</bag>
Pretty standard stuff to those in the know (of which I am still not). I also created the corresponding RuralLocation class and RuralLocation.hbm files to go along with them.
So the above error would appear anytime I tried to get a session factory in NHibernate, which is pretty much the first thing that's done anywhere in the app. The ultimate solution: RuralLocation.hbm was not an embedded resource.
This is something I will forget to do about 75% of the time I create a new .hbm file but usually the error is more "oh yeah"-inducing. Something like "Can't find class Trilogy.Gunton.Domain.RuralLocation" where you can tell pretty quickly it's not loading the file properly. I know this because the way I discovered the problem is by creating an integration test to save a RuralLocation on its own, outside the context of its parent object.
In this case, because I went straight to using the new class in a collection within another object, the error was hair-tearingly cryptic. Luckily, I don't have much hair anyway so no harm done.
Kyle the Eggheaded
Thursday, January 17, 2008
Am playing with ASP.NET MVC these days in preparation for a couple o' presentations on it. Jeffrey Palermo's CodeCampServer sample app has proven invaluable in this regard even if I have become overly cognizant that I'm ripping off his ideas.
In my own version, I'm also referencing NHibernate in my data access layer and whenever I do that, I always forget which assemblies it requires to tag along with it. In my sample, I brought over NHibernate.dll, Iesi.Collections.dll, and log4net.dll into my lib folder and added the reference to NHibernate.dll from the project. Wired up all my repositories, hooked in the IoC container (in this case, Windsor) and loaded the page:
Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Not overly helpful and although my lead-up to this has made it painfully obvious, let me tell you when you've also added the IoC container and three other layers 'twixt the UI and the DataAccess layer, 'tweren't altogether clear just what was causing this problem.
In fact, even when I reverted all the code in the control back to use the simplest possible controller action, I still got this error. The reason being: I still had the reference to the DataAccess project as well as the references to its classes in the Windsor container. Which means it was still trying to load NHibernate.
And NHibernate (or at least the version of it I have) requires, along with the other assemblies mentioned above, a reference to Castle.DynamicProxy.dll. So I dropped the assembly into the lib folder and it came along for the ride the next time I compiled. Problem solved. Reminder stored.
Kyle the Proxied
*UPDATE* I'm resurrecting this bad boy at the request of one of my more moderate commenters below to reward him for his tact.
Those of you looking for some resources on using NHibernate with MVC, the best I can think of at the moment would be to review the S#arp Architecture source code and sample application. http://code.google.com/p/sharp-architecture/
Even if you can't use the architecture itself, it is a great learning tool for how to work with NHibernate within MVC. While it uses Fluent NHibernate, there are notes on using it with plain NHibernate as well.
You could also check out Code Camp Server (http://code.google.com/p/codecampserver/) though I haven't looked at it in a while. Also, check out the Summer of NHibernate series: http://www.summerofnhibernate.com/. Session 13 is the one on using it in web applications but I'm told it's all gold.
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent
my employer's view in any way.
Copyright © 2010 Kyle Baley. All rights reserved.
|
|
|
CATEGORIES
|
|
LATEST POSTS
POPULAR POSTS
|
|
ARCHIVE
| March, 2010 (1) |
| February, 2010 (8) |
| January, 2010 (2) |
| December, 2009 (3) |
| November, 2009 (2) |
| September, 2009 (5) |
| August, 2009 (4) |
| July, 2009 (2) |
| June, 2009 (5) |
| May, 2009 (6) |
| April, 2009 (5) |
| March, 2009 (6) |
| February, 2009 (2) |
| January, 2009 (6) |
| December, 2008 (5) |
| November, 2008 (2) |
| October, 2008 (5) |
| September, 2008 (9) |
| August, 2008 (5) |
| July, 2008 (7) |
| June, 2008 (6) |
| May, 2008 (11) |
| April, 2008 (13) |
| March, 2008 (13) |
| February, 2008 (12) |
| January, 2008 (19) |
| December, 2007 (16) |
| November, 2007 (8) |
| October, 2007 (23) |
| September, 2007 (15) |
| August, 2007 (8) |
| July, 2007 (6) |
| June, 2007 (11) |
| May, 2007 (19) |
| April, 2007 (14) |
| March, 2007 (3) |
| February, 2007 (4) |
| January, 2007 (7) |
| December, 2006 (5) |
| November, 2006 (9) |
| October, 2006 (11) |
| September, 2006 (14) |
| August, 2006 (11) |
| July, 2006 (15) |
| June, 2006 (8) |
| May, 2006 (10) |
| April, 2006 (12) |
| March, 2006 (3) |
| February, 2006 (7) |
|
|
|