Asbestos Supply

2010-06-01 EntityFramework caching woes

When you retrieve an entity from the database, Entity Framework handles lazy loading for you by creating a proxy for your model class.  There is, however, a little caveat.  Take the following code:

``` using (MSEntities ent = new MSEntities("name=MSEntities")) { // I already added Microsoft to the database, with ID 1 Employee billGates = new Employee() { // We're not setting Company here, but there is a Company property CompanyID = 1, EmployeeName = "Bill Gates" }; ent.Employees.AddObject(billGates); ent.SaveChanges();

int employeeId = billGates.EmployeeID;
var retrievedBill = ent.Employees.Single(x => x.EmployeeID == employeeId);

}

On line 12 we set a variable called retrievedBill to an employee that we retrieve from the DB with the same ID as the one we just added – so in theory we should have a full Employee object setup and ready to go.  Except that's not what happens.  In fact, retrievedBill's Company property is null!  However, if we try the same line -- ent.Employees.Single(x => x.EmployeeID == 1) – inside its own Entites context, we'll get the full Employee and its Company property will not be null.  What's going on here?

Well, the reason is simple but far from expected (IMO).  As mentioned, EF handles lazy loading for you by creating a proxy for your model class.  This proxy inherits from your class, so a proxy for Employee might be EmployeeProxy123456789 (run through your code and you'll notice the actual type for a retrieved employee looks something like this) and it overrides the getters for navigation properties and enables lazy loading (I went a little more depth on how this works at 2010-05-28 EntityFramework should throw more exceptions!

However, EF also has a cache so that you don't have to retrieve your entities from the DB on every call.  This is smart, right?  I mean if we have a 4 places that retrieve Employee 1, why should we have to do 4 SQL statements?  On line 10 above, when we add billGatesto the EF context, billGates is cached.  Now if you step through this code, you'll notice that on line 14 retrievedBill is not a proxy – it's the actual Employee that we instantiated!  That's why Company is null -- there's no proxy to override the getter and do the lazy loading for us!

Let's just say that this is not how I would have build EF.  In my opinion, EF should wrap even cached objects in proxies (if they're not already) so that lazy loading works as expected…