A Look at Lazy Loading in EF4

Previous "What's New in EF 4 Posts"

ef4

 

Lazy Loading is a feature that many ORM tools including LINQ to SQL support by default, but does not exist in EFv1. I appreciate not having my code hit the database without me asking for it, but there are plenty of times when you will want your code to be helpful enough to go get related data when you ask for it even if you didn't specifically load it into memory.

EF4 now supports lazy loading, although it is OFF by default. For the person who wants to have control over trips to the database, off by default is a good thing. If you are coming from the datareader,dataset,datatable world, this is pretty normal. However for the person who loads up some Customers and then prints out a report that says that not one of the customers has any orders because they forgot to load the orders or they expected them to just be there, ON by default might be better.

Either way, it is off by default which will help this align better with existing code.

Lazy Loading is a ObjectContext setting, not an application setting.

That means you have to explicitly turn it on for each instantiated context. You can get the lazy loading behavior through one of the new ContextOptions settings: DeferredLoadingEnabled.

context.ContextOptions.DeferredLoadingEnabled=true;

Once the setting is on, even entities which were already in memory will benefit.

For example, if you query for some customers, then set DeferredLoading to true and then  iterate through the customers asking for related info, lazy loading will work.

 List<Customer> customers = context.Customers.ToList(); context.ContextOptions.DeferredLoadingEnabled = true;
 foreach (Customer cust in context.Customers)
   Console.WriteLine("Customer {0}, Account {1}", 
cust.Person.LastName.Trim() + ", " + cust.Person.FirstName, cust.AccountNumber);

And it will stay on for further queries against that context.

The most important thing to be aware of is the impact this has behind the scenes. Because I have 30 customers in the database, there were 31 trips to the database - the initial query to get the Customers and then 30 separate queries executed to get the Person for each Customer as I iterated through the result set.

Lazy Loading vs. Eager Loading

Compare that to eager loading with context.Customers.Include("Person") which will execute a single query. If I'm sure that I want every Person for every Customer that I load, it might make more sense to just load them all in advance.

As long as you are aware of the impact, then you have what you need to decide when Lazy Loading is right and when eager loading is the proper choice. There is still one other thing to consider which is the size of the query and the result set being returned from the database when you eager load. Check my earlier blog post about eager loading and database performance. [The cost of eager loading in Entity Framework]

I remember when I first started using LINQ to SQL and watching SQL profiler. I queried for categories, bound the resultset to a databinding control in ASP.NET and then in the markup for the web page, built in logic to display the products for each category. As the page loaded and read each category, L2S executed a new query to get the products for each category as it was being rendered. This mean 40 or so more trips to the database to get the products for each of the 40 categories. The reason I keep harping on this point is not because the 40 queries are a bad thing per se, but they are a bad thing if the developer is unaware that this is happening and has not made an explicit choice to interact with the database in this way. (Devil's advocate also reminds me about that same developer who will just assume there are no products because they didn't explicitly load the products themselves. But they would have the same problem if they were working with DataReaders/DataSets/DataTables.)

DeferredLoading combined with Include

You can get the best of both worlds, however. If you find that on one context, you would like to eager load some data and lazy load other data, there's no problem.

context.ContextOptions.DeferredLoadingEnabled = true; 
foreach (Order order in context.Orders.Include("Items")) Console.WriteLine("Order #: {0}, Line Item Count: {1}", order, order.Items.Count());

Checking the SqlProfiler, I see that there is only one query excecuted for this. So even though lazy loading is on, the context knows not to lazy load the items again.

DeferredLoading and objects already in memory

This led me to wonder how EF was making that decision. I dug around in Reflector and found some of the lazy loading implementatino but could not get far enough to find the answer. So I did another test.

I eager loaded just one customer with its person record.

Then I loaded all of the customers, but without eager loading the Person for those.

context.ContextOptions.DeferredLoadingEnabled = true;
Customer cust70 = context.Customers.Include("Person").FirstOrDefault();
List<Customer> customers = context.Customers.ToList();
foreach (Customer cust in context.Customers)
  Console.WriteLine("Customer {0}, Account {1}", 
cust.Person.LastName.Trim() + ", " + cust.Person.FirstName, cust.AccountNumber);

Because of the success of the earlier Include test, I was expecting that there would be no lazy loading for that customer as I iterated through the entire set of customer. I was surprised to see that it did get requeried after all along with all of the other Person queries.

Always On All The Time?

If you want to be sure that DeferredLoadingEnables is always true and that you don't have to set it every time, you can easily get around this with your own business logic, for example implementing the OnContextCreated property in the context's partial class. This will still be per context, not per the application, but you can go even further and build a wrapper for System.Data.ObjectContext if you want the setting to be automatically on 100% of the time.

Shout it
#1 Rob on 5.23.2009 at 12:09 PM

Unfortunately, having written 3 versions of my own ORM and using advanced OSS ORM solutions for the last five years, having put every application I've developed on the .NET platform in production using an ORM, I know that this solution to lazy loading is not adequate for real world scenarios.Controlling lazy loading at the context level not only ties business logic to ORM implementation and DB concerns, but it is not a granular enough solution.Lazy Loading should be able to be defined on a per Reference/Collection basis for every entity in the model, with a reasonable convention being applied when it is not specified.It is not a question of whether you should or should not use lazy loading, but a question of which parts of your model should use it as the default behavior and which parts should use some other strategy as the default.

#2 Julie on 5.23.2009 at 12:48 PM

Good (and educational!) perspective. Understand that I come from the world of datasets so lazy loading is new to me as of Linq to SQL.So this is not even per type, but you are saying that I should be able to define how the Orders collection of a customer is loaded or how the Person property of a Customer is loaded, right? That makes sense.There may be something else up EF4's sleeve though. Note this from the ADONET Team blog post on POCO:"Deferred (Lazy) loading is supported with POCO through the use of proxy types that are used to provide automatic lazy loading behavior on top of your POCO classes. This is something that we’ll cover when we get to deferred loading."So the separation should be there and perhaps there is enough support there to add in the per property control.??julie

#3 Mikael Henriksson on 5.28.2009 at 6:52 PM

First of all cheers for the book! :) I think it should not be too hard to manipulate your custom object context I am trying out the POCO support (nothing autogenerated for me, it's too easy). I think it should be quite possible to turn this on wherever you want depending on the approach you take. Would love to see a post on that if you are up for the challenge!

#4 Mikael Henriksson on 5.28.2009 at 9:03 PM

I have a couple of links for you Julie ;) First I created a simple way of enabling/disabling the ContextOptions in my blog post: http://blog.zoolutions.se/post/2009/05/29/EF4-e28093-Customizing-the-OjectContext-for-LazyLoading!.aspx

Second the ADO.NET team updated their series on POCO and lazy loading on: blogs.msdn.com/.../poco-in-the-ent

#5 Julie on 5.28.2009 at 9:16 PM

Thanks Mikael. I did see that post on the team blog today and it doesn't look like we'll get a chance to apply the lazy loading per collection. I bet with a lot of hacking, there could be a way to accomplish this.

I checked out your blog post and like your overloads!

#6 Niels on 11.19.2009 at 1:05 PM

Hi Julie,

Is there a simpel way to check if Navigation properties are loaded or not (POCO Lazy loading off)? I tried it via the RelationshipManager but could not figure out a way to determine the correct RelatedEnd if there are more than one. Any help with this one?

Kind Regards

#7 Julie on 11.19.2009 at 1:27 PM

with entityobjects (not poco objects) it's fairly simple if you are explicitly working with the objects. You can call:

aCustomer.Orders.IsLoaded()

It will be true if:

a) you eager loaded with Include

b) you loaded with Load

c)if lazy loading were on (I know you aid it is off), loaded via lazyloading

Will not be true otherwise. Even if you ran a separate query that pulled in the orders.

If you are tryingi to do this generically, it's a bunch of work to read the metadata, determine what navigation properties exist, see if they are IsLoaded==true and then figure out which navigation it is.

At least that's how I would do it ..maybe there's an easier way in EF4 but Id on't know of it.

hth

#8 YuRukai on 5.18.2010 at 1:03 AM

great .Is there Chinese Edition Of your Book ??

Kind Regards

^ ^

Leave a Comment