Include LINQ Extension
Updated for NHibernate
Updated thanks to a comment by Samuel Jack! Thanks, Samuel!
As you know, different LINQ O/RMs offer different ways to eagerly fetch associations:
- NHibernate offers several methods, including the HQL fetch keyword, extension method IQueryable<T>.Fetch and ICriteria.SetFetchMode
- Entity Framework has ObjectQuery.Include, which, shamelesly, takes a string instead of an expression
- LINQ to SQL has DataLoadOptions.LoadWith, which has some problems, and DeferredLoadingEnabled, which is all or nothing, and you cannot use on a per-query basis
The extension method below forces loading of any associations (direct or indirect) with the query you specify, and can be fluently combined in order to support multiple associations:
public static IQueryable<T> Include<T, TInclude>(this IQueryable<T> query, Expression<Func<T, TInclude>> path) where T: class where TInclude : class { if (query is ObjectSet<T>) { //Entity Framework Int32 i = path.Body.ToString().IndexOf('.'); String pathString = path.Body.ToString().Substring(i + 1); return ((query as ObjectSet<T>).Include(pathString)); } /*else if (query is NhQueryable<T>) { //NHibernate return((query as NhQueryable<T>).Fetch(path)); }*/ else { //LINQ to SQL and others ParameterExpression pathParameter = path.Parameters.Single(); Type tupleType = typeof(Tuple<T, TInclude>); Expression<Func<T, Tuple<T, TInclude>>> pathSelector = Expression.Lambda<Func<T, Tuple<T, TInclude>>>(Expression.New(tupleType.GetConstructor(new Type[] { typeof(T), typeof(TInclude) }), new Expression[] { pathParameter, path.Body }, tupleType.GetProperty("Item1"), tupleType.GetProperty("Item2")), pathParameter); return (query.Select(pathSelector).Select(t => new { Item1 = t.Item1, Item2 = t.Item2 }).Select(t => t.Item1)); } }
If you are using .NET pre 4.0, you also need to define class Tuple:
class Tuple<T1, T2> { public Tuple(T1 item1, T2 item2) { this.Item1 = item1; this.Item2 = item2; } public T1 Item1 { get; private set; } public T2 Item2 { get; private set; } }
Sample code:
using (MyDataContext ctx = new MyDataContext()) { var myItems = from i in ctx.MyItems.Include(itm => itm.MyOtherItems).Include(itm => itm.MyOtherItems.MyAnotherItems) select i; }
What it does is add a select to the original expression which places the original expression result plus the associated path in a Tuple object, and then only selects the original expression from it. I tested it with LINQ to SQL and Entity Framework, and it seems to be working fine. Feedback is appreciated, as always!