繁体   English   中英

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

[英]How to pass lambda 'include' with multiple levels in 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);
    }

在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'方法,因为查询本身位于存储库内部,服务没有访问。 有没有办法治愈它?

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

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

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

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

...将转换为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;
    }
}

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

这篇文章的样本将成为

var plan = _unitOfWork.PlanRepository
            .Include(x => x.PlanSolutions)
            .ThenInclude(x => x.Solution)
            .FirstOrDefault(p => p.Id == id);
    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);
                }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM