Igo*_*gor 2 c# asp.net lambda include entity-framework-core
我有一个存储库,可以获得'include'的lambda表达式.
public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includePaths)
{
return Context.Set<TEntity>().Includes(includePaths).FirstOrDefault(predicate);
}
Run Code Online (Sandbox Code Playgroud)
在EF的早期版本中,我在服务层中使用它,例如:
var plan = _unitOfWork.PlanRepository
.FirstOrDefault(
p => p.Id == id,
include => include.PlanSolutions.Select(ps => ps.Solution)
);
Run Code Online (Sandbox Code Playgroud)
其中'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 Core 中,您应该能够使用此处所述的ThenInclude方法。
这篇文章的样本将成为
var plan = _unitOfWork.PlanRepository
.Include(x => x.PlanSolutions)
.ThenInclude(x => x.Solution)
.FirstOrDefault(p => p.Id == id);
Run Code Online (Sandbox Code Playgroud)
实体框架核心牺牲了易于理解的API的参数化.实际上,在EF6中,将多级Include表达式传递给方法要容易得多.在ef-core中几乎是不可能的.
但是Include接受属性路径作为字符串的方法仍然存在,因此如果我们可以将旧式多级Include表达式转换为路径,我们可以将路径提供给基于字符串的路径Include.
幸运的是,这正是EF6引擎盖下发生的事情.而且由于EF6是开源的,我不必重新发明轮子,但可以轻松借用他们的代码来实现我们想要的.结果是一个扩展方法AsPath,它将lambda表达式作为属性路径返回.您可以在方法中使用它将includes参数转换为可以添加Includes 的字符串序列.例如,表达式......
include => include.PlanSolutions.Select(ps => ps.Solution)
Run Code Online (Sandbox Code Playgroud)
......将被转换成PlanSolutions.Solution.
如上所述:EF6为核心部分提供信用.唯一的主要修改是我的方法在两个最常尝试的不支持的功能中抛出异常:过滤和排序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;
}
}
Run Code Online (Sandbox Code Playgroud)
public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includePaths)
{
DbSet = Context.Set<TEntity>();
var query = includePaths.Aggregate(DbSet, (current, item) => EvaluateInclude(current, item));
return query.Where(predicate).FirstOrDefault();
}
private IQueryable<T> EvaluateInclude(IQueryable<T> current, Expression<Func<T, object>> item)
{
if (item.Body is MethodCallExpression)
{
var arguments = ((MethodCallExpression)item.Body).Arguments;
if (arguments.Count > 1)
{
var navigationPath = string.Empty;
for (var i = 0; i < arguments.Count; i++)
{
var arg = arguments[i];
var path = arg.ToString().Substring(arg.ToString().IndexOf('.') + 1);
navigationPath += (i > 0 ? "." : string.Empty) + path;
}
return current.Include(navigationPath);
}
}
return current.Include(item);
}
Run Code Online (Sandbox Code Playgroud)