Every time we get an entity from EF the related entities are not automatically loaded. They will be loaded at the first attempt to access, so that from user’s point of view anything changes. This mechanism is called Lazy loading. Here is a little example:
Person person; using (var cont = new TestDBEntities()) { person = cont.Persons.First(p => p.IdPerson == 1); var cityName = person.City.CityName; var genderName = person.Gender.GenderName; var carDescription = person.Cars.First().CarDescription; }
This code works with no problems. The related entities City, Gender and Cars are automatically downloaded at first access, so we can read their fields without realizing that the Lazy Loading is working behind the scenes. We can instead realize it with the following change:
Person person; using (var cont = new TestDBEntities()) { person = cont.Persons.First(p => p.IdPerson == 1); } var cityName = person.City.CityName; var genderName = person.Gender.GenderName; var carDescription = person.Cars.First().CarDescription;
In this case we can see the Lazy Loading at works, because at the first attempt to access a related entity we’ll get an exception telling that: “The ObjectContext instance has been disposed and can no longer be used for operations that require a connection”.
What does it means? It means that when we first try to access to read a property, the Lazy Loading attempt to load the related entity from the database, failing because the EF context has been disposed. If we look at the related entities in this moment they are all = null. This is a situation that we can easily have if we work with MVC Framework, where we could have some code like this within our controller:
public ActionResult MyAction() { using (var cont = new TestDBEntities()) { return View(cont.Persons.First(p => p.IdPerson == 1)); } }
If we try to access a related entity from the Razor view related with this action we’ll get the same error. This because when the Razor view is processed the controller’s action has already returned and our EF context has already been disposed.
So how can we do if we are in this situation? There are many solution, the simplest one it to explicitly pre-load all the related entities. This mechanism is called Explicit Loading and works taking advantage on the EF cache, where all the entities loaded from previous queries are stored to be reused. EF only uses the Lazy Loading for related entities not yet in the cache. Those which are already present will be automatically assigned without needing any extra access to the DB. Here is an example:
Person person; using (var cont = new TestDBEntities()) { person = cont.Persons.First(p => p.IdPerson == 1); cont.Cities.Where(c => c.IdCity == person.IdCity).Load(); cont.Genders.Where(g => g.IdGender == person.IdGender).Load(); cont.Cars.Where(c => c.IdPerson == person.IdPerson).Load(); } var cityName = person.City.CityName; var genderName = person.Gender.GenderName; var carDescription = person.Cars.First().CarDescription;
If we try to run this example we’ll see that the City and the Gender are loaded, while the related Cars are still not present. That’s because the Explicit Loading only works with related entities on the N-side of a 1:N relationship. The Cars are on the 1-side of a 1:N relationship, so EF can’t be sure that the pre-loaded Cars in its cache are all the cars needed, so it leave the reference = null. To have the Cars loaded as well we need to change our code in this way:
Person person; using (var cont = new TestDBEntities()) { person = cont.Persons.First(p => p.IdPerson == 1); cont.Cities.Where(c => c.IdCity == person.IdCity).Load(); cont.Genders.Where(g => g.IdGender == person.IdGender).Load(); cont.Entry(person).Collection(p => p.Cars).Load(); } var cityName = person.City.CityName; var genderName = person.Gender.GenderName; var carDescription = person.Cars.First().CarDescription;
Another approach is to ask EF to directly include the related entities we are interested in while is loading our person. This mechanism is called Eagerly Loading and is probably the most clean we can use:
Person person; using (var cont = new TestDBEntities()) { person = cont.Persons.Include(p => p.City) .Include(p => p.Gender).Include(p => p.Cars).First(p => p.IdPerson == 1); } var cityName = person.City.CityName; var genderName = person.Gender.GenderName; var carDescription = person.Cars.First().CarDescription;