Previous "What's New in EF 4 Posts"
- Upgrading EF projects to EF4- Don't forget to target .NET 4.0 May 22
- Customizing EDM Code Gen in EF4 May 21
- Complex Types in the EDM Designer in EF4 and a look at updating complex types in code May 21
- The much improved EDM Wizard Pluralization in VS2010 May 21
- Checking for EF to TSQL Query Compilation Changes in VS2010 Beta1 May 19
- EF4- What is and is not supported May 13
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.




