tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Include with filtering or limiting

6 Mar 2012 3 mins Entity Framework, Expressions, LINQ

Almost four years ago I wrote an article Load with filtering or limiting. You might want to do same stuff with Include method. It’s not directly supported in this method but you can do it with anonymous object easily. There’s only one catch, it’s the anonymous object. That means, if you need the entities you need to manually select these out on IEnumerable<T> (doing it on query isn’t going to work because Entity Framework will (correctly) figure out you’re not using the pieces of anonymous object and will change the generated query appropriately).

Small example:

context.EntitySet
	.Select(x => new { Entity = x, Related = x.Related.OrderBy(y => y.FooBar).Take(6) })
	.AsEnumerable()
	.Select(x => x.Entity)
	.ToArray();

Nothing fancy. But could it be wrapped inside method so you don’t have to type it over and over again. In comments of above mentioned post same question was raised. Well if I can write it manually, why it shouldn’t be possible to extract it into method, right? Don’t know whether this method has any real usage, but it was a nice exercise.

public static IEnumerable<TMain> Include2<TMain, TOther>(this IQueryable<TMain> source, Expression<Func<TMain, IEnumerable<TOther>>> path)
	where TMain : class
{
	TMain main = default(TMain);
	var dummy = new { Main = main, Included = Enumerable.Empty<TOther>() };
	Expression<Func<TMain, object>> exampleExpression = x => new
	{
		Main = x,
		Included = Enumerable.Empty<TOther>()
	};
	PropertyInfo mainProperty = dummy.GetType().GetProperty("Main");
	ParameterExpression accessParam = Expression.Parameter(dummy.GetType(), "x");
	Delegate mainBack = Expression.Lambda(Expression.MakeMemberAccess(accessParam, mainProperty), accessParam).Compile();
	NewExpression body = (exampleExpression.Body as NewExpression);
	Type anonymousType = body.Type;
	var parametersMap = exampleExpression.Parameters.Select((f, i) => new { f, s = path.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
	Expression reboundBody = ParameterRebinder.ReplaceParameters(parametersMap, path.Body);
	NewExpression newExpression = body.Update(new[] { body.Arguments[0], reboundBody });
	LambdaExpression finalLambda = Expression.Lambda(newExpression, exampleExpression.Parameters);
	MethodInfo miA = typeof(Queryable).GetMethods()
		.Where(m => m.Name == "Select")
		.Where(m => m.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericArguments().Count() == 2)
		.First();
	MethodInfo miB = typeof(Enumerable).GetMethods()
		.Where(m => m.Name == "Select")
		.Where(m => m.GetParameters()[1].ParameterType.GetGenericArguments().Count() == 2)
		.First();
	MethodInfo selectAMethod = miA.MakeGenericMethod(typeof(TMain), anonymousType);
	var dataA = selectAMethod.Invoke(null, new object[] { source, finalLambda });
	MethodInfo selectBMethod = miB.MakeGenericMethod(anonymousType, typeof(TMain));
	var dataB = selectBMethod.Invoke(null, new object[] { dataA, mainBack });
	return dataB as IEnumerable<TMain>;
}

The method is doing little bit of magic with expressions and anonymous type to do what you would normally write by hand. It returns IEnumerable<T> because the object needs to be unwrapped locally so you can’t add additional pieces into query. I’m using ParameterRebinder from Colin Meek’s post to properly rewire parameters. Same functionality can be also found in Mono.Linq.Expressions.

From input parameters types (yeah, the power of static typing [Anders Hejlsberg]) you can see how to use it:

context.EntitySet
	.Include2(x => x.Related.OrderBy(y => y.FooBar).Take(6))
	.ToArray();

That’s all. 😎

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.