Entity Framework core sacrificed ease of parametrization for a more comprehensible API. Indeed, in EF6 it was much easier to pass multi-level Include
expressions to a method. In ef-core that's virtually impossible.
But the Include
method accepting a property path as string still exists, so if we can convert the old-style multi-level Include
expression to a path, we can feed the path into this string-based Include
.
Fortunately, this is exactly what happened under the hood in EF6. And since EF6 is open source, I didn't have to reinvent the wheel but could easily borrow their code to achieve what we want. The result is an extension method AsPath
that returns a lambda expression as a property path. You can use it inside your method to convert the includes
parameter to a sequence of strings by which you can add the Include
s. For example, the expression ...
include => include.PlanSolutions.Select(ps => ps.Solution)
... will be converted into PlanSolutions.Solution
.
As said: credits to EF6 for the core part of the source. The only major modification is that my method throws exceptions in two of the most commonly attempted unsupported features: filtering and ordering an Include
. (Still not supported in ef-core).
public static class ExpressionExtensions
{
public static string AsPath(this LambdaExpression expression)
{
if (expression == null) return null;
var exp = expression.Body;
string path;
TryParsePath(exp, out path);
return path;
}
// This method is a slight modification of EF6 source code
private static bool TryParsePath(Expression expression, out string path)
{
path = null;
var withoutConvert = RemoveConvert(expression);
var memberExpression = withoutConvert as MemberExpression;
var callExpression = withoutConvert as MethodCallExpression;
if (memberExpression != null)
{
var thisPart = memberExpression.Member.Name;
string parentPart;
if (!TryParsePath(memberExpression.Expression, out parentPart))
{
return false;
}
path = parentPart == null ? thisPart : (parentPart + "." + thisPart);
}
else if (callExpression != null)
{
if (callExpression.Method.Name == "Select"
&& callExpression.Arguments.Count == 2)
{
string parentPart;
if (!TryParsePath(callExpression.Arguments[0], out parentPart))
{
return false;
}
if (parentPart != null)
{
var subExpression = callExpression.Arguments[1] as LambdaExpression;
if (subExpression != null)
{
string thisPart;
if (!TryParsePath(subExpression.Body, out thisPart))
{
return false;
}
if (thisPart != null)
{
path = parentPart + "." + thisPart;
return true;
}
}
}
}
else if (callExpression.Method.Name == "Where")
{
throw new NotSupportedException("Filtering an Include expression is not supported");
}
else if (callExpression.Method.Name == "OrderBy" || callExpression.Method.Name == "OrderByDescending")
{
throw new NotSupportedException("Ordering an Include expression is not supported");
}
return false;
}
return true;
}
// Removes boxing
private static Expression RemoveConvert(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked)
{
expression = ((UnaryExpression)expression).Operand;
}
return expression;
}
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…