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
);