Motivation
When I was trying Entity Framework after many years with NHibernate, I assumed the Entity Framework has the same transparent lazy loading behavior like NHibernate.
But the assumption was not completely right. There are some minor behavior differences in Entity Framework. In this article, I will explain what lazy loading is,
how NHibernate and Entity Framework implement it, and the similarities and differences.
What is Lazy Loading
Quote from Wikipedia:
Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed.
It can contribute to efficiency in the program’s operation if properly and appropriately used. The opposite of lazy loading is Eager Loading.
When there is a domain model in an application, the entity in the domain model is used to represent the business entity in the problem domain. The association between the entities is equivalent
to the relationship or interaction of business entities. The beauty of the domain model is it can be used to mimic the problem domain as much as we can.
However, the domain object in memory is volatility. Hence, we need tables in the database to sustain the domain object. The domain object needs to be saved to the database
or loaded back into memory from the database. The code for loading a domain object from the database is not trivial. Sometimes this kind of code uses the majority of developer time
in application development. On the other hand, the code for loading a domain object is very similar for any domain entity. Therefore, an ORM framework is introduced
to provide unified data access code. The ORM framework relieves the developer from doing the nitty-gritty database access code and let him focus on more important business
logic code. By using an ORM framework, the domain object is loaded or saved with a developer friendly API.
The domain object in a domain model is not independent usually. In most situations, multiple domain objects are linked with each other as a big domain object graph.
When the application gets a particular domain object from a database, we need to also load objects of related domain entities. Lazy loading is invented to load related
domain objects on-demand. By doing this, the developer will directly work with the domain object and use it naturally without worrying how the related domain objects are loaded.
The easiest way to support lazy loading is to have customized code in properties and methods to initialize a related domain object when it is going to be used.
To add customized lazy loading code is a lot of work, time consuming, and error-prone. Fortunately, this kind of cross-cutting code can be injected by
an AOP framework, such as the Windsor Castle AOP framework. This is called transparent lazy loading. The precondition for using transparent lazy loading is to make any property or method
lazy loading friendly. This means marking a property or method as virtual
in C#. The benefit of lazy loading is deferring query execution to the moment
the application really needs it and reducing application memory usage.
How transparent lazy loading works in NHibernate
NHiberenate uses the Windsor Castle AOP framework to implement lazy loading. Let’s take a look at how to get lazy loading working in NHibernate:
- Make class properties and methods accessible and overload-able.
Lazy loading can only be added for public or protected virtual properties or methods. Hence if your class properties and methods are private, you can’t use lazy loading.
Also, lazy loading is not available on class fields.
public class Employee
{
public virtual int EmployeeID { get; set; }
public virtual string Name { get; set; }
public virtual Iesi.Collections.Generic.ISet<Task> Tasks { get; set; }
}
- Turn on lazy loading in the HBM mapping file.
The lazy loading attribute is by default true, though you can still explicitly specify it in the HBM mapping file.
="1.0" ="utf-8"
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Demo" namespace="Demo">
<class name="Employee">
<id name="EmployeeID">
<generator class="identity" />
</id>
<property name="Name" />
<set name="Tasks" lazy="true" inverse="true" cascade="all-delete-orphan">
<key column="EmployeeID"/>
<one-to-many class="Demo.Task"/>
</set>
</class>
</hibernate-mapping>
- Return domain object from session.
The domain object must be returned back from session, such as using the Session.Get
method with the domain entity ID or using Session.CreateQuery
with an HQL query.
var employee = session.Get<Employee>(1);
Or:
var employee =
session.CreateQuery("from Employee as e where e.EmployeeID = 1"
).List<Employee>().FirstOrDefault();
If the domain object is attached to the NHibernate session, you will not have lazy loading on it. Internally, NHibernate uses the Windsor Castle AOP framework to implement
lazy loading. The Windsor Castle AOP framework subclasses a domain class and overrides all public and protected virtual properties and methods to provide logic for loading
related domain objects during first time access. The subclass is named as <Class>Proxy
.
The Session.Get
method is the place letting NHibernate create an instance of the subclass to replace the real domain object. The code using the created
object is the same for the proxy object and the real domain object. The lazy loading process is transparent to the developer; if you want to know whether the object is loaded,
you can use NHibernateUtil.IsInitialized
to verify it.
Assert.IsFalse(NHibernateUtil.IsInitialized(task.Employee));
If you don’t want to have lazy loading enabled because of the N+1 performance issue or you know you will use all related objects in your application, you can turn
lazy loading off by setting the lazy
attribute to false.
<set name="Tasks" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="EmployeeID"/>
<one-to-many class="Demo.Task"/>
</set>
By set lazy attribute to false, NHibernate will do implicate eager loading for you.
How transparent lazy loading works in Entity Framework
The earlier versions of Entity Framework didn’t have the transparent lazy loading feature. This was introduced in version 4.0. Because there is no transparent lazy loading
design upfront, this feature in Entity Framework is designed differently to be backwards compatible with the Entity Framework older version. If you have POCO domain classes
in your application, you are required to do the following to have lazy loading enabled in Entity Framework:
- Make class properties and methods override-able and accessible.
This step is similar to NHibernate, Entity Framework uses a subclass to inject additional code into the domain class for lazy loading.
Note: this only applies to the POCO style domain class; if your domain classes are generated with the EntityModelCodeGenerator
template, it should already contain explicit
lazy loading code, so no subclass is needed. Below is a code snippet generated with the EntityModelCodeGenerator
template.
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("LazyLoadingModel", "FK_Task_Employee", "Task")]
public EntityCollection<Task> Tasks
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.
GetRelatedCollection<task>("LazyLoadingModel.FK_Task_Employee", "Task");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.
InitializeRelatedCollection<task>("LazyLoadingModel.FK_Task_Employee", "Task", value);
}
}
}
- Specify true for “Lazy Loading Enabled” in edmx properties.
You can actually specify Lazy Loading Enabled in your context class directly. However, the default place is in the edmx file. The Lazy Loading Enabled setting
will eventually get into your context class if it’s automatically generated, like LazyLoadingEntities
in my example.
public partial class LazyLoadingEntities : ObjectContext
{
public const string ConnectionString = "name=LazyLoadingEntities";
public const string ContainerName = "LazyLoadingEntities";
#region Constructors
public LazyLoadingEntities()
: base(ConnectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public LazyLoadingEntities(string connectionString)
: base(connectionString, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
public LazyLoadingEntities(EntityConnection connection)
: base(connection, ContainerName)
{
this.ContextOptions.LazyLoadingEnabled = true;
}
#endregion
#region ObjectSet Properties
public ObjectSet<Employee> Employees
{
get { return _employees ??
(_employees = CreateObjectSet<employee>("Employees")); }
}
private ObjectSet<employee> _employees;
public ObjectSet<task> Tasks
{
get { return _tasks ?? (_tasks = CreateObjectSet<task>("Tasks")); }
}
private ObjectSet<task> _tasks;
#endregion
}
- Return domain object from context.
The domain object that needs to be lazy loaded must be returned from the context. A lazy loaded object looks like this in the Visual Studio 2010 Watch window:
If you don’t want to have lazy loading in Entity Framework, you can turn it off by setting LazyLoadingEnabled
to false
.
this.ContextOptions.LazyLoadingEnabled = false;
After lazy loading is turned off, Entity Framework will not load the related objects for you anymore. If you access a navigation property that is not loaded,
you will get a NullReferenceException
error. Or, when you access a navigation collection property that is not loaded, you will get an empty collection.
This confuses me when I first use Entity Framework after years with NHibernate.
Caveat on Using Lazy Loading
In both NHibernate and Entity Framework, lazy loading is enabled by default. This is the best practice for most situations. By enabling lazy loading,
the developer doesn’t need to worry how related objects are loaded. Also, untouched reference objects or collections do not occupy memory space.
However, we need to be careful on the N+1 problem incurred by lazy loading in some situations. The N+1 problem is when you have lazy loading enabled
for a domain object that has a collection property that contains N items, you will have one select query to be executed to retrieve data for the domain object itself,
and then you will have every single query executed for each item in the collection. In total, you execute N+1 queries to get all data in the domain object
and collection property instead of just one query. This causes too many queries to the database. To avoid the N+1 problem, we need to use a profiler monitor execution
of the application to find the potential spot that has this problem, and then use explicit eager loading or batch loading to avoid or mitigate this problem.
Using the Code
The code is developed in Visual Studio 2010. You need to create database LazyLoading in SQL Server, and then run the attached
CreateTables.sql script to create tables and data in it. Before running the code, remember to update
the connection string in the configuration file to your local connection string.