27 Feb 2011

A Smarter Entity Framework Include Method

6 Comments Uncategorized

One of the things I have always disliked about Entity Framework and their support for LINQ is that while your whole LINQ statement is compile time checked to make sure you didn’t fat finger any SQL statement, the Include() statement is not. 

How many times has something like this happened to you?

var account1001 = (new AccountEntities())
    .Accounts.Include("Userx")
    .Where(account => account.Id == 1001)
    .SingleOrDefault();

You might have noticed in the above that I probably wanted "Users" instead of "Userx". I don’t know about you but this happens to me all the time, and if the code was just compile time checks like the rest of the LINQ statement everything would be great and I would instantly catch the bug when the code was compiled.

I decided to make the Include method a little smarter with the use of Expressions and Extension methods, so that my include statements can be checked at compile time.  Here is how I did it.

public static ObjectQuery<T> Include<T>(this ObjectQuery<T> source, Expression<Func<T, object>> exp)
{
    var path = GetPath(exp);
    return source.Include(path);
}

private static string GetPath(Expression exp)
{
    switch (exp.NodeType)
    {
        case ExpressionType.MemberAccess:
            var name = GetPath(((MemberExpression)exp).Expression) ?? "";

            if (name.Length > 0)
                name += ".";

            return name + ((MemberExpression)exp).Member.Name;

        case ExpressionType.Convert:
        case ExpressionType.Quote:
            return GetPath(((UnaryExpression)exp).Operand);

        case ExpressionType.Lambda:
            return GetPath(((LambdaExpression)exp).Body);

        default:
            return null;
    }
}

The first method called Include has the same signature as the Include method included with the Entity Framework, except instead of taking a string for the path, the one I created above takes an Expression. The second method called GetPath, turns the expression in to a representitive string. So if your expression is:

x => x.Users

It will provide a string with the same signature:

"Users"

So here in the original statement from above using the new compile time checked include statement:

var account1001 = (new AccountEntities())
    .Accounts.Include(account => account.Users)
    .Where(account => account.Id == 1001)
    .SingleOrDefault();

As we were all taught in COMPSCI 101 or learned the hard way, magic strings are bad, and how Entity Framework implemented the Include method is a form of magic string that can easily cause unexpected bugs or even crashes.  So my effort above, hopefully will help some developer avoid a bug that should have been easily avoidable by a simple compile. 

Tags: , ,
written by
Nick Berardi
subscribe
If you found this post valuable and would like to see more like it you can follow me.

6 Responses to “A Smarter Entity Framework Include Method”

  1. Reply Ryan says:

    This is pretty cool, but still does all the work at runtime to give you a compile time benefit. You might like this better: http://www.esenciadev.com/2011/01/getting-rid-of-magic-strings-in-entity-framework-4-includes

    • Reply Nick Berardi says:

      To be honest Ryan, that is very ugly solution and really doesn’t solve any problems. Yours still does work at runtime, it just takes static strings and merges them at runtime. Plus you really should be using constants, if you use this method, and not static readonly, because you are getting none of the benefits of the compiler optimization of strings by concating static strings together.

      You still have to put a period in between so there is a chance you could do:

      .Include(Job.IncludeReferences.ForLocation + Location.IncludeReferences.ForStateProvince)

      And it would still compile, but be a total disaster at runtime. Same with the fact if you used two properties that don’t even related to each other.

      The point of my example above was to have the compiler at compile time check to make sure the properties exist on the objects that are referencing them.

Leave a Reply