Improving ObjectQuery.Include – Updated
Having spent some time using the sample from my previous post on ObjectQuery.Include, I’ve encountered a bug! It turns out that the code generates the wrong include string for
context.Customers.Include(c => c.Order.SubInclude(o=>o.OrderDetail))
The fix for this is a small change to the BuildString method to recurse up the MemberExpression if necessary. The updated code is below - usual disclaimers apply!
public static class ObjectQueryExtensions
{
public static ObjectQuery<TSource> Include<TSource, TPropType>(this ObjectQuery<TSource> source, Expression<Func<TSource, TPropType>> propertySelector)
{
string includeString = BuildString(propertySelector);
return source.Include(includeString);
}
private static string BuildString(Expression propertySelector)
{
switch (propertySelector.NodeType)
{
case ExpressionType.Lambda:
LambdaExpression lambdaExpression = (LambdaExpression)propertySelector;
return BuildString(lambdaExpression.Body);
case ExpressionType.Quote:
UnaryExpression unaryExpression = (UnaryExpression)propertySelector;
return BuildString(unaryExpression.Operand);
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)propertySelector;
MemberInfo propertyInfo = memberExpression.Member;
if (memberExpression.Expression is ParameterExpression)
{
return propertyInfo.Name;
}
else
{
// we've got a nested property (e.g. MyType.SomeProperty.SomeNestedProperty)
return BuildString(memberExpression.Expression) + "." + propertyInfo.Name;
}
case ExpressionType.Call:
MethodCallExpression methodCallExpression = (MethodCallExpression)propertySelector;
if (IsSubInclude(methodCallExpression.Method)) // check that it's a SubInclude call
{
// argument 0 is the expression to which the SubInclude is applied (this could be member access or another SubInclude)
// argument 1 is the expression to apply to get the included property
// Pass both to BuildString to get the full expression
return BuildString(methodCallExpression.Arguments[0]) + "." +
BuildString(methodCallExpression.Arguments[1]);
}
// else drop out and throw
break;
}
throw new InvalidOperationException("Expression must be a member expression or an SubInclude call: " + propertySelector.ToString());
}
private static readonly MethodInfo[] SubIncludeMethods;
static ObjectQueryExtensions()
{
Type type = typeof(ObjectQueryExtensions);
SubIncludeMethods = type.GetMethods().Where(mi => mi.Name == "SubInclude").ToArray();
}
private static bool IsSubInclude(MethodInfo methodInfo)
{
if (methodInfo.IsGenericMethod)
{
if (!methodInfo.IsGenericMethodDefinition)
{
methodInfo = methodInfo.GetGenericMethodDefinition();
}
}
return SubIncludeMethods.Contains(methodInfo);
}
public static TPropType SubInclude<TSource, TPropType>(this EntityCollection<TSource> source, Expression<Func<TSource, TPropType>> propertySelector)
where TSource : class, IEntityWithRelationships
where TPropType : class
{
throw new InvalidOperationException("This method is only intended for use with ObjectQueryExtensions.Include to generate expressions trees"); // no actually using this - just want the expression!
}
public static TPropType SubInclude<TSource, TPropType>(this TSource source, Expression<Func<TSource, TPropType>> propertySelector)
where TSource : class, IEntityWithRelationships
where TPropType : class
{
throw new InvalidOperationException("This method is only intended for use with ObjectQueryExtensions.Include to generate expressions trees"); // no actually using this - just want the expression!
}
}
UPDATE: Alex James has a great post on Eager Loading Strategies that I'd recommend reading.
Comments
Anonymous
April 24, 2009
PingBack from http://www.anith.com/?p=32024Anonymous
June 13, 2010
The formatting of this blog makes it impossible to read the code, it clips the text to the right.Anonymous
June 13, 2010
Hi Thomas - I agree! blogs.msdn.com got a major upgrade recently and I've not yet had chance to go through and try to fix the formatting issues that it introduced. The code does seem to copy correctly if that's any helpAnonymous
June 13, 2010
In EF4 you can include for EntityCollection and then to digg deeper on he type of the collection. Is your solution support it?Anonymous
June 13, 2010
Hi Noam, The example at the top of the post: context.Customers.Include(c => c.Orders.SubInclude(o=>o.OrderDetails)) corresponds to context.Customers.Include("Orders.OrderDetails") Is that what you are referring to? --StuartAnonymous
May 02, 2014
Does this work in VS 2012 with Framework 4.0?Anonymous
May 12, 2014
@Mark - I've definitely used this approach with EF 4, EF 5 and EF 6. If you're working with the DbQuery API rather than ObjectQuery API then there are a few tweaks that you need to make to the code, but the approach still works