Lazy ORM users deserve it!


Let’s say you build an app that uses a traditional ORM concept of lazy loading. Your app binds an Order object to a grid and lets the user lazily load related objects like OrderLines. Your user – a normal cubicle dweller, queries for an Order and then decides to go get some coffee.  She comes back, navigates to OrderLines and …


Oops! Some data changed in between the time the Order was explicitly queries and the OrderLines were implicitly lazily loaded. Of course, the ORM builders know and understand the semantics – maybe the app developer (ORM user) also gets it. But how about the app user?


OK. So I have to admit I am not a fan of lazy loading – by any name – deferred loading, demand loading, just-in-time loading, blah loading. For me, it is all about including what you want and then shutting off the darned spigot that brings in the objects. No more lazy stuff coming in from who knows what state and who knows in what amounts!


All users are welcome to follow this way. Otherwise, lazy ORM users deserve “it”. But what is “it”?
– Exposure to concurrent changes in the database. So you can get a frankengraph instead of a regular object graph.
– The abomination of getting all the objects that were ever stored in the database but were afraid to load if you weren’t doing lazy loading. How about ten years worth of orders for a given Customer instead of getting the two Orders from last week?


OK. So the purists say – you get your complete object – any piece of code that I pass my Customer object to knows that it can see the full set of Orders – only one dangerous navigation away. Think of it like nice little landmines. Ouch, that one blew a couple of megs!! Arrgh – that one ruined consistency of my data.


What about you? Are you wandering in a minefield 😉


If not, we have just the right thing for you. More about it in the next instalment.


Comments (14)

  1. Hackee says:

    It’s not the lazy-loading’s fault; it’s all about concurrency and stale data. If lazy-loading supports optimistic concurrency check?

    Order anOrder = …



    anOrder.OrderLines; //Lazy loading.

    // translate to sql query like:

    // SELECT *  FROM [OrderLines]AS [t0]

    // WHERE [t0].[OrderID] = @p0 AND [t0].LastModified = @p1

  2. Marc Stevens says:

    I tend to agree with the previous comment … This situation is not a ‘lazy-loading problem’; it is about how the ‘whatever-loading’ design solution copes with the isolation requirements upon the persistent data layer.

  3. This isn’t particularly an ORM problem either if you are considered about the app user. Maintaining the consistency of a view with the data model is a balancing act that must consider user-interface conventions, user expectations and performance.

    Eager fetching/lazy loading is a design choice you must make whether you use an ORM or not. Lazy load makes a lot of sense for many scenarios, because I only want to see those OrderLines for a particular Order. Until I tell you which order that is, why would I want all the expense of retreiving them?

    If the object graph is going to be used for commit operations then we have to consider it differently.

  4. I (and we – those at my shop – by extension) get around this largely by applying Domain-Driven Design; looking for aggregates, aggregate roots, and adding facilities to our repositories that allow us to specify graph navigation during persistence operations.  We do this over ORM frameworks and tend to avoid lazy loading features of frameworks.  I find that N+1 is a domain library design issue and is solved by design rather than by data access black magic.

  5. Well said Scott. That makes sense to me too – more of a personal choice.

    Lazy loading is unfortunately too ingrained into a segment of ORM users. It is convenient but not a good practice.

  6. I agree with you Jeremy about certain valid scenarios (e.g. data dependent or user selected data). Unfortunately, like many other features, it is often used without enough thought about concurrency. Data access APIs (ORM or otherwise), do provide some protection in case of updates but don’t do so for lazy loading – for the right reasons of course. It is an overkill to jam such feature into lazy loading and also quite hard.

  7. Well said Scott. That makes sense to me too – more of a personal choice.

    Lazy loading is unfortunately too ingrained into a segment of ORM users. It is convenient but not a good practice.

  8. rape stories says:

    Best of the text i read about a problem.

  9. The first few comments are correct, the issue is not lazy vs. eager loading, nor is it orm vs. non-orm, the issue is how do you handle concurrency. It seems like the author is confusing the issues, he seems to think eager loading prevents concurrency issues. If anything it increases the chances of having an issue (bacause concurrency is an issue of action vs. time). In either case, concurrency must be thought of for any system you design, orm or not, lazy or not.

  10. GQAdonis says:

    I believe you can support a variant of lazy loading by implementing what I have come to call a "modes" feature in your data access interface (somebody else might have a better name for it).  

    A "mode" is like a personality of an entity object identified by name and associated with the object’s potential use in the layers above the data layer.  For instance, a simple details view in user interface has no reason to retrieve objects past the first "level" of an object graph, while a tree view might want to retrieve every "current" level plus one.

    When requesting objects from your persistence mechanism, the "query" (perhaps a Query Object implementation) can include the "mode" requested.  That way one can retrieve the right kind of object suited for maximum performance and correctness for the given situation.

  11. Ben Kloosterman says:

    Lazy loading is just a form of caching.

    Just like caching – you have the same problem with dirty results in the cache. The more static the data the better.

    I dont like automatic lazy loading but it can improve both code and performance.

    Caching transactional data is always a risk, which is most cases is not worth it. Mostly static data ( Customers , Products , unalterable orders etc) are  good candidates.

    With SOA designs its a huge issue. If you create an Order Service and a Product Service , every time the Order Service requests a product it has to do a cross app boundary to avoid things running loke a dog you lazy load. ( Though since it is static data it is an easier choice).

    Regards,

    Ben

  12. Anders Borum says:

    I agree with most of the comments.

    Unless you’re certain you’ll require those related data (e.g. order > products), there’s no reason to load them. Which is why it’s important that the LINQ to SQL infrastructure allows the developer to choose the design pattern that fits the requirements (or target enviroment).

    When designing the architecture of an application (or an API), one may choose to implement lazy loading for a number of reasons.

    The idea about lazy loading (always) leading to performance degradation is incorrect. It depends on the requirements and the implementation by the developer.

    Provided your data are available as entities, lazy loading can be coupled with a central cache tier that provides for a very performant architecture (distributing cloned entities is much faster than asking the database).

    It’s all about how you design for concurrency – which is a very tough question to answer.

    If you’re designing an API for 3rd party users (where each unique request to the API is served through e.g. a context), it may be appropriate to allow the context to navigate the object graph and retrieve objects that were modified (even deleted) post the creation of the current context – but when the context attempts to change data, the context is aware of stale data (i.e. by comparing timestamps or a corresponding mechanism).

    For ASP.NET applications, the above pattern works quite well because you’d usually want the benefits of lazy loading (for performance issues and memory pressure) as well as allowing the particular request to complete without sporadic null references (i.e. when navigating Order > Customer where the customer entity may have been deleted post the creation of the current context).

    I guess this pattern is some sort of transactional serialization between contexts.

    I’m glad to see that LINQ to SQL supports (most of) the design decisions made by the developer. Lazy loading or shaped queries ..

    Great discussion – any comments on these thoughts?!

  13. When using an OR/M like LINQ To SQL, it is vitally important in almost all instances to understand the