如何在 Entity Framework Core 中传递具有多个级别的 lambda 'include'? [英] How to pass lambda 'include' with multiple levels in Entity Framework Core?

查看:34
本文介绍了如何在 Entity Framework Core 中传递具有多个级别的 lambda 'include'?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个获取包含"的 lambda 表达式的存储库.

I have a repository that gets a lambda expression for 'include'.

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

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

In previous versions of EF I used it in services layers like:

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

PlanSolutions"是一个集合,Solution"是嵌套在PlanSolution"中的一个属性.

Where 'PlanSolutions' is a collection and the 'Solution' is a property nested from 'PlanSolution'.

但是现在这段代码出错了:

But now this code gets an error:

InvalidOperationException: 属性表达式 'include => {fromPlanSolutions ps in [include].PlanSolutions select [ps].Solution}' 是无效.该表达式应表示属性访问:'t =>t.MyProperty'.有关包含相关数据的更多信息,请参阅http://go.microsoft.com/fwlink/?LinkID=746393.

InvalidOperationException: The property expression 'include => {from PlanSolutions ps in [include].PlanSolutions select [ps].Solution}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

现在看来我不能使用选择"方法来获取多个级别包含,但我也不能使用微软建议的然后包含"方法,因为查询本身位于服务不存在的存储库内部无权访问.有什么方法可以治愈吗?

Now it seems that I can't use 'Select' method for getting multiple levels include, but I also can't use 'ThenInclude' method that Microsoft suggests, because the query by itself located inside of the repository where service doesn't have an access to. Is there any way to heal it?

推荐答案

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

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.

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

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.

幸运的是,这正是 EF6 引擎盖下发生的事情.而且由于 EF6 是开源的,我不必重新发明轮子,而是可以轻松地借用他们的代码来实现我们想要的.结果是一个扩展方法 AsPath,它返回一个 lambda 表达式作为属性路径.您可以在方法中使用它来将 includes 参数转换为字符串序列,您可以通过该字符串序列添加 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 Includes. For example, the expression ...

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

... 将被转换为 PlanSolutions.Solution.

... will be converted into PlanSolutions.Solution.

如前所述:源代码的核心部分归功于 EF6.唯一的主要修改是我的方法在两个最常尝试的不受支持的功能中引发异常:过滤和排序 Include.(在 ef-core 中仍然不支持).

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;
    }
}

这篇关于如何在 Entity Framework Core 中传递具有多个级别的 lambda 'include'?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆