Long story: you've been warned...
My company inherited an application from overseas developers filled with an ungodly horror of WTFs. Among other things, all data access in the application used untyped datasets and datatables, meaning that values in the dataset were accessed by string field name and cast to the appropriate type. The app contained hundreds and hundreds of stored procedures, all of which looked like they were intentionally written as badly as possible.
In backlash to the nightmare of maintaining the application's current data access code, I suggested that the existing data access layer be phased out in favor of type-safe objects. Since no one wanted to rewrite the stored procedures, I also suggested using an ORM which would write SQL for us and make database access completely transparent.
At the time, I had experience with NHibernate, but I really hated creating all of those dozens of bulky, fragile the XML config files. I suggested using Castle ActiveRecord, which is a handy wrapper on top of NHibernate -- it uses attributes instead of XML to map objects to the database.
In retrospect, the whole "lets replace the data access layer with an ORM" sounded much easier on paper than in practice. For a start, the original programmers passed around datarows between hundreds of methods. You can't just swap out a datarow with a typed object, because many times the authors wrote code like this:
public void PrintReport(DataRow[] rows)
{
foreach (DataRow row in rows)
{
if (row.Table.Columns.Contains("Balance"))
{
Decimal balance = Convert.ToDecimal(row["Balance"]);
// do stuff with the balance column
}
// 50 to 100 more if statements exactly like this
}
}
Basically, "all-knowing" methods were written to handle any kind of row input regardless of the data it contained. This made it hard to constrain parameter inputs to a single datatype or interface.
Even worse, since each method had dependencies on two dozen other methods, every method we changed to take a typed object required changes the two dozen other methods, and changes to each of those two dozen methods required even more changes to code in a cascade-like effect.
By the time we implemented typed objects, we ended up gutting and dismantling half the application and just re-writing it. Bugs galore!
We dedicated a one 1-week minisprint iteration just to replace the existing data access layer with Castle ActiveRecord objects. At the time, no one on my team had ever worked with Castle or NHibernate, and I'd be lying if I said there was no learning curve. Our 1-week iteration extended to 3 weeks.
Not only was the rewrite bug-ridden, we have a new problem now: NHibernate + Castle ActiveRecord = a crap ton of reflection behind the scenes. We expected a slight performance hit from the reflection overhead, but this was too much. Even the simplest queries such as "SELECT [cols] FROM [table]" took 30 seconds to execute, and we couldn't do anything to fix the problem. The application used to start up in a few seconds, now it takes several minutes.
Crap. All that work we put into writing prestine code made the application too slow to be usable, and large parts of the old code had been trashed beyond repair. The ActiveRecord changes needed to be reverted. :(
We lost a few weeks of development, and I've since learned a valuable character building lesson about massive refactors of existing code.