Wednesday, December 30, 2009

How do I create type safe includes with Entity Framework?

A question you might come across is - "How do I include other entity sets when doing a query so as not to make multiple round trips to the database? Calling ".Include" in your LINQ statements provides eager loading when retrieving objects to the database. To take a step back: let's consider this scenario:

You have two linked tables - "Car" and "Maker". When you select a car object, you will have a linked entity called "Maker" and vice versa - if you select "Maker", you will have a linked entityset "Cars". If you try calling one of these objects, EF will automatically make a call to the database for you to return the linked object.

So you say, I don't want to do that! I want to get the Car, and the Maker in one shot to the database. You would think it'd be something like creating a join in the query, but not so. You need to call ".Include" like so:


using (var db = new DatabaseEntities())
return db.Cars.Include("Maker").Where(c => c.Color == "Red").FirstOrDefault();


or for query syntax:

return (from c in db.Cars.Include("Maker") where c.Color equals "Red" select c).FirstOrDefault();


The problem is we are passing a string into the Include. This is bad. So I set out on creating a type safe Include extension. Here it is:


public static IQueryable<T> Include<T>(this IQueryable<T> mainQuery, Expression<Func<T, object>> subSelector)
{
var selector = subSelector.Body as MemberExpression;
var expression = selector.Expression;
var includeString = new StringBuilder();

//Go through and insert parent expressions + "." (insert puts them before like prepend)
while (expression != null && expression.Type.Name != subSelector.Parameters[0].Type.Name)
{
includeString.Insert(0, ".");
includeString.Insert(0, expression.Type.Name);
expression = ((MemberExpression)expression).Expression;
}

//The actual name we were looking to get in the first place
includeString.Append(selector.Member.Name); //The base type we want to get

return ((ObjectQuery<T>)mainQuery).Include(includeString.ToString());
}

Basically we extend an IQueryable object. These are, for the most part, our ObjectSets (mapped to database tables). You pass in a lambda expression specifying the entity you want to retrieve eagerly, and it loops back from that and creates a string out of it. You can use it like this:

db.Cars.Include(c => c.Maker)

You can also call several layers down like so:

db.Cars.Include(c => c.Maker.Country)

No comments:

Post a Comment