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!

Bookmark and Share

                             

7 Comments

  • Hi,
    I've tried this out using LinqPad, and I can't get it to work for Linq-to-Sql, unfortunately.

    To make sure that Include is actually doing something, I've made sure to set DeferredLoadingEnabled = false.

    When I do this, the collections that I try to load with Include come back null.

    Looking at the SQL output in Linqpad, I think I see why: using the Include expression adds a join to the sql query, but it doesn't actually select any columns from the joined table - hence no data for the child objects is actually loaded.

    In addition to this, if you have multiple Include calls, only the collection referred to by the first one is include in the SQL join.

    This is a pity, because DataLoadOptions is perhaps the biggest pain-point in Linq-to-SQL, and having proper Include support would be very powerful.

  • Hi, Samuel!
    Try now... Thank you very much for your feedback!
    Keep in touch! ;-)
    RP

  • Vb.net samples would be appreciated as a supplement, 30% of ppl still prefer to use it!

  • Erana,
    Sorry, I don't know VB well enough... but if you need help, let me know!
    In the meantime, you can use http://www.developerfusion.com/tools/convert/csharp-to-vb/

  • Ricardo,
    No luck with that either, I'm afraid.

    As before, everything compiles and runs with out throwing any exceptions, it just doesn't actually fetch the data for the child collections.

  • Samuel:

    If it's OK with you to have the Include extension as the last statement, and to not being able to have several of them in a single query, you can do this:

    return (query.Select(pathSelector).Select(t => new { Item1 = t.Item1, Item2 = t.Item2 }).AsEnumerable().Select(t => t.Item1));

    And change the method return type to IEnumerable.

    RP

  • EF Feature CTP4 includes a new Include extension method in the System.Data.Entity namespace that allows you to specify include paths with a lambda.

Comments have been disabled for this content.