In my first post about the 4.0 beta 1 bits I explored how we can work with our own POCO classes with the Entity Framework. In this post I will expand the example a little and figure out if and how we can do automatic lazy loading. Furthermore i will see if I can use the build in T4 template support to autogenerate an ObjectContext for our POCO entities.
The Model
I want to create a common scenario with an order, orderline, product and customer, so I start out with writing the following classes:
1 public class Customer
2 {
3 public virtual ICollection<Order> Orders { get; set; }
4 public virtual int Id { get; set; }
5 public virtual string FirstName { get; set; }
6 public virtual string LastName { get; set; }
7 public virtual string Address { get; set; }
8 public virtual string Email { get; set; }
9 }
10 public class Order
11 {
12 public virtual ICollection<OrderLine>
13 OrderLines { get; set; }
14 public virtual Customer Customer { get; set; }
15 public virtual int Id { get; set; }
16 public virtual DateTime CreatedDate { get; set; }
17 public virtual int Status { get; set; }
18 }
19 public class OrderLine
20 {
21 public virtual Product Product { get; set; }
22 public virtual Order Order { get; set; }
23 public virtual int Id { get; set; }
24 public virtual double ItemPrice { get; set; }
25 public virtual int Quantity { get; set; }
26 }
27 public class Product
28 {
29 public virtual ICollection<OrderLine>
30 OrderLines { get; set; }
31 public virtual int Id { get; set; }
32 public virtual string Name { get; set; }
33 public virtual string Description { get; set; }
34 public virtual double Price { get; set; }
35 }
Then I go ahead and create the Entity Framework Model exactly like the Poco classes.
Notice the navigation properties have the right pluralization. If you generate your Entity Model from an existing database notize that you now have the option to pluralize or singularize generated object names:
T4 Code Generation
This time I dont want to manually write my objectcontext implementation. I want to code generate it, so that when i update my Entity Model the ObjectContext is automatically updated as well.
In version 1.0 of the Entity Framework you had to create a SingleFileGenerator and hook into EFs codegeneration events, and it was not a very likeable costumization story. Because of all the feedback on this, The ADO.Net Team decided to handle code generation with T4 templates instead.
This is truly sweet, and as it is deeply integrated with the Entity Framework its very easy to get started. As an example we will add a t4 template generator to our project and modify it to work with our POCO classes.
First we select our .edmx file and go to the entity designer. Then we right-click anywhere on the workarea and choose “Add new Artifact Generation Item”. Then we choose the ADO.NET EntityObject Generator:
This creates a .tt file with a sub .cs file. The .tt file is the template and the sub cs. file is the result of running the code generation on the template. When you change the .tt file and save the .cs file will be updated – you can also right click the .tt file and choose “Run custom tool”.
Now we open op the template file, and its not really as nice an interface as we are used to with Visual Studio – no syntax-highlighting or intellisense really makes it a challenge to edit:
To fix that we use the brand new Extension Manager (Tools > Extension Manager) in Visual Studio to get the Free Edition of the Tangible T4 editor – its not as feature rich as the Clarius Visual T4 one, but its free and a huge improvement:
This makes our template file look a lot more appealing, and the intellisense is simply invaluable – trust me, you need this one. The .tt file now looks like this:
So now we are equipped to remove the bits of the code generation that we don’t want. We want to get rid of any entities being generated and leave only the context bits. Its basically three sections that need to go - see the attached project for the resulting file. We also need to add an import to the namespace that contains our Poco classes, as so that these classes are used instead of the generated entities that we just have removed.
Requirements for Using Persistence-Ignorant Objects
In order to support deferred loading of related objects and change tracking some requirements do need to be fulfilled by the POCO classes.
What happens behind the scenes is, that a proxy object is created that inherits from your POCO object. This proxy object is responsible for handling lazy loading and change tracking. The requirements are:
- Poco class must be public, and not sealed or abstract, and implement a default constructor with no parameters.
- Poco class must NOT implement IEntityWithChangeTracker or IEntityWithRelationShips.
- Poco class properties mapped to conceptual model must be public and virtual.
- The mapped Navigational property must be declared virtual and must not be sealed.
- One-to-Many and Many-to-Many navigational property (OrderLine on Order for instance) must be mapped to properties that return a type implementing ICollection.
There are actually generated two kinds of proxies: One that handles lazy loading and one that handles change tracking. If you only mark navigational properties virtual, you will not get the change tracking proxies.
Eager Load vs. Lazy Load
So now we want to try out out, and we could do something like this:
1
2using (var context = new OrderModelContainer())
3{
4 var orders = from o in context.OrderSet select o;
5
6 foreach (var order in orders)
7 {
8 Console.WriteLine(order.CreatedDate);
9 foreach (var orderline in order.OrderLines)
10 {
11 Console.WriteLine(" " + orderline.Quantity
12 + " " + orderline.Product.Name
13 + " of $" + orderline.ItemPrice);
14 }
15 }
16 }
We try to print the orderlines for each order – but the result is without the orderlines:
In Entity Framework version 1.0 we would have to explicitly eager load the related objects, and you can of course still do this:
6 var orders = from o in
7 context.OrderSet.Include("OrderLines.Product")
8 select o;
In many situations this is a good thing, as you have control over how many times your database is hit on a query, but it also clutters your code with at lot of explicit includes, and more often than not developers are surprised that the related collection of objects – orderlines for instance – is empty when called. It just seems more logical with lazy loading.
So instead you can now in EF4 decide to let the related objects load silently when they are called, resulting of course in further calls to the DB.
The lazy load or deferred load option is set on the context, and is disabled by default:
1 using (var context = new OrderModelContainer())
2 {
3 context.ContextOptions.DeferredLoadingEnabled = true;
4
5 var orders = from o in context.OrderSet select o;
6
7 foreach (var order in orders)
8 {
9 Console.WriteLine(order.CreatedDate);
10 foreach (var orderline in order.OrderLines)
11 {
12 Console.WriteLine(" " + orderline.Quantity
13 + " " + orderline.Product.Name
14 + " of $" + orderline.ItemPrice);
15 }
16 }
17 }
Both the eager loaded and the lazy loaded produces the following result:
Setting the lazy load on the context seems very limited, and it would be much more helpful to the developer in a real world scenario if you could define the load strategy per navigation property.
The best that i can see as an option is that you enable lazy loading, and then handle the cases you know are going to do a lot of DB roundtrips with eager loading and do a sort of combo.
All in all i still think that EF4 is gigantic leap forward, and so far it has been surprisingly easy to make everything run as expected.
Resources