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

标签 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);
    }

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

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

其中“PlanSolutions”是一个集合,“Solution”是从“PlanSolution”嵌套的属性。

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

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.

现在看来我不能使用“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。唯一的主要修改是我的方法在两个最常尝试的不受支持的功能中抛出异常:过滤和排序 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;
    }
}

关于c# - 如何在 Entity Framework Core 中传递具有多个级别的 lambda 'include'?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47052419/

相关文章:

python - 使用 pandas df 的具有多个 if 的 lambda 函数

C# 更优雅地替代 foreach-search

c# - 项目之间的交互

ASP.NET Site.css 不会改变

c# - 创建没有 lambda 的任务

C# lambda 和数组中的升序值

c# - winform的优化

C# asp.net 使用mysql数据库的值动态创建多个复选框

c# - ASP.NET 页面不应在提交时回发整个页面

asp.net - 使用asp.net将Excel数据导入sql数据库