Lazy-loading in an ORM considered harmful?

One of the features that looks insanely cool in many ORMs is Lazy loading of properties. Say you have an "Order" table that stores orders of a customer. Inside your Order business object you have a list of Line Items which is linked to the Products table and you have the customer record, which is linked to the Customer table.

If you want to display a list of Orders, you don’t touch line items, and thus only fetch entries from the Orders table. But in the details page of the order, you fetch the products as well. The cool thing: In your repository, it is only a generic Get/GetAll method to get it.

How awesome is this? Lazy loading is amazing, it makes the code so much shorter! Except… it’s a ticking time bomb. If you return a live database object from the repository into your services and views, someone at some point will think "Hey, we should display the order total as well!" and change the code to put in a Orders.LineItems.Sum(li => li.Total). Well, congratulations, you just introduced a SELECT N+1 into your code!

If you are not careful and just chain lazy collections (e.g., a Product may have a List that contains all orders the product is part of.), you may end up fetching the full database. Several times over. For every line in your list. Of course, you can tell your front-end developers to be careful, and of course you should have monitoring (like MVC Mini Profiler) from the very start so that you can catch it early. However, it is also a philosophy to fall into the pit of success by default and really have to climb out of it.

I’m no longer using generic repositories with generic Get/GetAll methods. I have a specific OrderRepository that has a method for IList<order> GetOrderDetails(IEnumerable<int> orderIds) which only populates fields it needs and throws an exception on stuff that doesn’t exist (like Products) or even returns a special type like OrderDetails, and then a GetOrder method that has the full object. Note that I’m returning an IList, not an IQueryable or IEnumerable – by the time it leaves the repository, it is fully populated and disconnected from the database.

Sure, sometimes you end up with a Service method that is literally just a call to the repository, but you still separate business logic into the service layer vs. persistance logic into the repository layer.