如何在Entity Framework Core中传递具有多个级别的lambda’include’?

我有一个存储库,可以获取’include’的lambda表达式。

public TEntity FirstOrDefault(Expression<Func> predicate, params Expression<Func>[] includePaths) { return Context.Set().Includes(includePaths).FirstOrDefault(predicate); } 

在以前的EF版本中,我在服务层使用它,例如:

 var plan = _unitOfWork.PlanRepository .FirstOrDefault( p => p.Id == id, include => include.PlanSolutions.Select(ps => ps.Solution) ); 

其中’PlanSolutions’是一个集合,’Solution’是一个嵌套在’PlanSolution’中的属性。

但现在这段代码出错了:

InvalidOperationException:属性表达式’include => {来自[include]中的PlanSolutions ps。.PlanSolutions select [ps] .Solution}’无效。 表达式应表示属性访问:’t => t.MyProperty’。 有关包含相关数据的详细信息,请参阅http://go.microsoft.com/fwlink/?LinkID=746393 。

现在似乎我不能使用’Select’方法获得多个级别包含,但我也不能使用Microsoft建议的’ThenInclude’方法,因为查询本身位于存储库内部,服务没有访问。 有没有办法治愈它?

entity framework核心牺牲了易于理解的API的参数化。 实际上,在EF6中,将多级Include表达式传递给方法要容易得多。 在ef-core中,这几乎是不可能的。

但是接受属性路径作为字符串的Include方法仍然存在,因此如果我们可以将旧式多级Include表达式转换为路径,我们可以将路径提供给这个基于字符串的Include

幸运的是,这正是EF6引擎盖下发生的事情。 而且由于EF6是开源的,我不必重新发明轮子,但可以轻松借用他们的代码来实现我们想要的。 结果是一个扩展方法AsPath ,它返回一个lambda表达式作为属性路径。 您可以在方法中使用它将includes参数转换为一系列字符串,您可以通过它们添加Include 。 例如,表达式……

  include => include.PlanSolutions.Select(ps => ps.Solution) 

…将转换为PlanSolutions.Solution

如上所述:EF6为核心部分提供信用。 唯一的主要修改是我的方法在两个最常尝试的不支持的function中抛出exception:过滤和排序Include 。 (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; } } 

接受的答案有点过时了。 在较新版本的Entity Framework Core中,您应该能够使用此处所述的ThenInclude方法。

这篇文章的样本将成为

 var plan = _unitOfWork.PlanRepository .Include(x => x.PlanSolutions) .ThenInclude(x => x.Solution) .FirstOrDefault(p => p.Id == id);