Yes, it is 2023 and yes I'm still using NHibernate at some of my projects(sorry old habits die hard).
One of the things that is really handy but can bite you in the foot as easily, is lazy loading.
What is lazy loading?
Lazy loading is used to delay the retrieval of related data from a database until it is actually accessed or requested by the application. ORM frameworks, like NHibernate or Entity Framework, map database tables to objects in your programming language, making it easier to work with relational data in an object-oriented manner.
When an ORM employs lazy loading for related data, it means that the ORM does not fetch all the associated data immediately when you query for the main entity. Instead, it loads the related data from the database only when you explicitly access or request that data. This approach helps improve performance by reducing the initial amount of data fetched from the database and minimizing the number of database queries.
Here's an example to illustrate how lazy loading works in the context of ORM:
Let's say you have two entities, Author
and Book
, with a one-to-many relationship (an author can have multiple books). With lazy loading enabled, if you retrieve an Author
object from the database, the ORM might only fetch the author's information and not load the associated books immediately. Only when you access the books
property of the Author
object will the ORM issue an additional query to fetch the books associated with that author.
Benefits of lazy loading in ORM:
-
Reduced Data Transfer: Lazy loading minimizes the amount of data transferred from the database to the application initially, which can lead to faster query execution and lower network traffic.
-
Efficient Resource Usage: Since related data is loaded only when needed, resources like memory and network connections are used more efficiently.
-
Improved Performance: Loading related data on-demand can lead to faster initial query execution times, which is especially beneficial in scenarios where not all related data is required for every operation.
However, lazy loading can also introduce some challenges:
-
N+1 Query Problem: If lazy loading is used excessively and not managed properly, it can lead to the "N+1 query problem," where fetching a collection of entities results in N additional queries to load their related data. This can cause performance issues.
-
Unintentional Overhead: Developers need to be mindful of when and how they access related data to avoid triggering unnecessary database queries and performance bottlenecks.
-
Complexity: Managing lazy loading and ensuring that related data is loaded appropriately can add complexity to the application code.
To address these challenges, many ORM frameworks provide options for customizing lazy loading behavior, like specifying eager loading (loading related data along with the main entity) or utilizing batch loading techniques to minimize the number of queries. Developers should carefully consider the trade-offs between eager and lazy loading based on their specific application's requirements and performance considerations.
How lazy loading works in NHibernate
In NHibernate lazy loading is enabled by default. To make this work NHibernate uses out-of-the-box a proxy object. This proxy objects sits in between your object and an associated relation(for example between the Author and its Books).
Remark: To make this work NHibernate requires that all properties and methods in your object are declared as virtual if lazy loading is enabled.
Here is an example in NHibernate:
Lazy loading without a proxy
Although the usage of proxies is the default way in NHibernate it is a leaky abstraction and can result to unexpected behavior. For example as explained in this post, the following code will not work as expected:
We are checking the type of what is returned but this is not the object itself but instead a proxy instance.
To avoid this issue, you can change the lazy loading behavior in NHibernate to not using a proxy but fetch the actual object the first time you access the property.
This is how to do it using the conformist mapping:
Or using Fluent NHibernate: