user544511
user544511

Reputation: 759

Asynchronous queries and lazy loading in Entity Framework

We are using Entity Framework 4.2 with model first approach and DbContext code generation.

Let's say we have following data model in entity framework:

class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

class Address
{
    public string City; { get; set; }
}

The scenario is following:

  1. ViewModel wants to load some data from database
  2. A task (asynchronous operation) is created for loading data. This is because we don't want UI to freeze when data is being loaded.
  3. Task (which is executed in a separate thread) creates new database context and loads data (e.g. Person object) from database
  4. Task finishes and database context is destroyed.
  5. Main thread is notified about task's completion. Main thread can now access loaded Person object.
  6. A view tries to show person's name and address in a text box through a data binding
  7. The view accesses Person.Name (no problems here)
  8. The view accesses Person.Address.City -> OOPS! The database context has already been disposed (as the loading was done in separate thread) and because of lazy loading Person.Address is not accessible!

In phase 3, person is loaded in following way:

using (DatabaseContext context = new DatabaseContext())
{
    Person person = from p in context.Persons.FirstOrDefault();
    return person;
}

Ok, I am aware that (in theory) I could force loading of the Address object in two ways: 1) Use DbSet.Include, e.g:

context.Persons.Include("Address").FirstOrDefault();

2) Access Person.Address while database context is still alive as this will force address to be loaded

Person person = context.Persons.FirstOrDefault();
Address address = person.Address;
return person;

Of course the first one is the preferred solution because it is not so ugly as the second one (just accessing the property to force loading of data and then discarding the result IS ugly). Also, in case of a collection (e.g. list of persons), I would have to loop through the collection and access the Address separately for each person. The problem with first solution is that only DbSet has Include method and all other collections returned from queries have not. So, let's say I have following database structure

class Resource {}
class Person : Resource { public Address Address { get; set; } }
class Appointment { public IList<Resource> Resources { get; set; } }

and I want to load all specific appointments and include address in every resource that is person, I am in trouble (or at least I could not figure a way to write query for it). This is because context.Appointments.Resources is not of type DbSet but a ICollection which does not have Include method. (Ok, maybe in this case I could write a query somehow starting from context.Persons instead of context.Appointments so that I could use Include but there are many scenarios where this is not possible)


So basically the questions are:

  1. Is this a proper way to do asynchronous database access in the first place?
  2. How to solve problems with lazy loading? Turning lazy loading off is not a solution (unless it can be done only for specific entities?)

Upvotes: 4

Views: 3807

Answers (1)

Polity
Polity

Reputation: 15130

You can set an implementation of a include strategy at startup of your application or test.

First define an Extension method: Include that defines the actual including mechanism (as default by doing nothing)

/// <summary>
/// Extension methods specifically for include since this is essential for DomainContext but not part of IQueryable by default
/// </summary>
public static class QueryableIncludeExtensions
{
    public static IIncluder Includer = new NullIncluder();

    public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path)
         where T : class
    {
        return Includer.Include(source, path);
    }

    public interface IIncluder
    {
        IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class;
    }

    internal class NullIncluder : IIncluder
    {
        public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path)
             where T : class
        {
            return source;
        }
    }
}

Then create an EF specific includer implementation like:

internal class DbIncluder : QueryableIncludeExtensions.IIncluder
{
    public IQueryable<T> Include<T, TProperty>(IQueryable<T> source, Expression<Func<T, TProperty>> path)
        where T : class
    {
        return DbExtensions.Include(source, path);
    }
}

Finally, hook the DbIncluder implementation up in the project you need it for, in my case i did it like:

public class DomainContext : DbContext, IDomainContext
{
    static DomainContext()
    {
        // register the DbIncluder for making sure the right call to include is made (standard is null)
        QueryableIncludeExtensions.Includer = new DbIncluder();
    }

Now IQueryable always has the extension method: Include available. You can extend this to IEnumerable if you want. The actual implementation is simply set by QueryableIncludeExtensions.Includer

Upvotes: 2

Related Questions