This has been asked many times since the initial release of EF Core. Earlier prerelease versions of EF Core even were supporting it, but then it has been removed from EF Core code (I guess in order to promote the new Include
/ ThenInclude
pattern).
While Include
/ ThenInclude
pattern looks more clear (besides the current Intellisense issues), it has one major drawback - requires access to EntityFrameworkQueryableExtensions
, thus reference to Microsoft.EntityFrameworkCore
assembly. While params
Expression>` pattern has no such requirement.
The good thing is the one can relatively easily add that functionality. The EF6 source code is publicly available on GitHub, and from there we can see that it uses a method called TryParsePath to build dot separated string path which then is passed to the string
overload of Include
method.
The same can be applied in EF Core. We can probably use the EF6 code, but I'm going to provide my own version. It can be easily be seen that the supported constructs are member accessors or calls to method called Select
with 2 arguments, the second being LambdaExpression
.
Following is my interpretation of the above, encapsulated in two custom extension methods:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore
{
public static class IncludeExtensions
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> includePaths) where T : class
=> includePaths.Aggregate(source, (query, path) => query.Include(path));
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> includePaths) where T : class
=> source.Include(includePaths.Select(e => GetIncludePath(e?.Body)));
static string GetIncludePath(Expression source, bool allowParameter = false)
{
if (allowParameter && source is ParameterExpression)
return null; // ok
if (source is MemberExpression member)
return CombinePaths(GetIncludePath(member.Expression, true), member.Member.Name);
if (source is MethodCallExpression call && call.Method.Name == "Select"
&& call.Arguments.Count == 2 && call.Arguments[1] is LambdaExpression selector)
return CombinePaths(GetIncludePath(call.Arguments[0]), GetIncludePath(selector.Body));
throw new Exception("Invalid Include path.");
}
static string CombinePaths(string path1, string path2)
=> path1 != null ? path1 + "." + path2 : path2;
}
}
The first is simply helper for calling multiple string
includes (taken from my answer to Entity Framework Core 2.0.1 Eager Loading on all nested related entities). The second is the method in question, which converts the expressions to strings and call the first. The main work is done by GetIncludePath
private method which recursively processes the expression based on the aforementioned rules, plus one additional rule - when navigating bottom up, it should end with lambda parameter.
Now the implementation of the method is question is simple as that:
public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
=> set.Include(includeProperties);