Dynamic Include statements for eager loading in a query - EF 4.3.1

I wrote this method to retrieve any set of entity dynamically based on their types. I used the IDbEntity interface to provide a valid key to search the userId in all the classes. The Util.GetInverseProperties<T>() method is used to get the properties needed in the Include statement.

public IEnumerable<T> GetItems<T>(string userId) where T : class, IDbEntity
{
    var query = db.Set<T>().Where(l => l.UserId==userId);

    var props = Util.GetInverseProperties<T>();
    foreach (var include in props)
        query = query.Include(include.Name);

    return query
        .AsNoTracking()
        .ToList();
}
public interface IDbEntity
{
    public string UserId { get; set; }
}
public static List<PropertyInfo> GetInverseProperties<T>()
{
    return typeof(T)
        .GetProperties()
        .Where(p => Attribute.IsDefined(p, typeof(InversePropertyAttribute)))
        .ToList();
}

Giving the compiler a hint by using IQueryable<CampaignCreative> instead of var will work too.

IQueryable<CampaignCreative> query = db.CampaignCreatives;
// or
DbQuery<CampaignCreative> query = db.CampaignCreatives;

When using var the compiler infers DbSet<T> for query which is more specific than the type returned by Include (which is DbQuery<T> (=base class of DbSet<T>) implementing IQueryable<T>), so you can't assign the result to the query variable anymore. Hence the compiler error on the query = query.Include(include) line.


Make the query variable queryable:

public CampaignCreative GetCampaignCreativeById(int id, string[] includes)
{
    using (var db = GetContext())
    {
        var query = db.CampaignCreatives.AsQueryable();
        foreach (string include in includes)
        {
            query = query.Include(include);
        }

        return query
            .AsNoTracking()
            .Where(x => x.Id.Equals(id))
            .FirstOrDefault();                    
    }
}

I am more fond of the non-string expressive way of defining includes. Mainly because it doesn't rely on magic strings.

For the example code, it would look something like this:

public CampaignCreative GetCampaignCreativeById(int id) {
    using (var db = GetContext()) {
        return db.CampaignCreatives
            .Include(cc => cc.Placement)
            .Include(cc => cc.CreativeType)                    
            .Include(cc => cc.Campaign.Select(c => 
                 c.Handshake.Select(h => h.Agency)))
            .Include(cc => cc.Campaign.Select(c => c.Product)
            .AsNoTracking()
            .Where(x => x.Id.Equals(id))
            .FirstOrDefault();
    }
}

And to make those dynamic, this is how you do that:

public CampaignCreative GetCampaignCreativeById(
    int id, 
    params Expression<Func<T, object>>[] includes
) {
    using (var db = GetContext()) {
        var query = db.CampaignCreatives;
        return includes
            .Aggregate(
                query.AsQueryable(), 
                (current, include) => current.Include(include)
            )
            .FirstOrDefault(e => e.Id == id);
    }
}

Which is used like this:

var c = dataService.GetCampaignCreativeById(
     1, 
     cc => cc.Placement, 
     cc => cc.CreativeType, 
     cc => cc.Campaign.Select(c => c.Handshake.Select(h => h.Agency)),
     cc => cc.Campaign.Select(c => c.Product
);