将通用预先加载方法从EF6转换为EF Core

Din*_*rdo 6 c# entity-framework entity-framework-core .net-core

对于EF6,我在我的通用存储库中有一个方法,我将其暴露给所有服务层,以便根据需要使用任何嵌套属性从数据库中检索实体:

public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
{
    var queryable = set.AsQueryable();

    return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}
Run Code Online (Sandbox Code Playgroud)

这样,我可以通过以下方式使用该方法:

var data = repo.OldMethod(x => x.Papers, => x.People.Select(y => y.Addresses)).ToList();
Run Code Online (Sandbox Code Playgroud)

在EF6中,这将加载每个人的Papers导航属性,People导航属性和Addresses导航属性.正如预期的那样,这会在EFCore中引发异常.由于在EFCore中切换到Include - > ThenInclude方法,我不太清楚如何在我的服务层轻松复制它,我不想要任何有关EntityFramework的信息.

Iva*_*oev 4

自 EF Core 首次发布以来,这个问题已被多次询问。EF Core 的早期预发布版本甚至支持它,但后来它已从 EF Core 代码中删除(我猜是为了推广新的Include/ThenInclude模式)。

虽然Include/ThenInclude模式看起来更清晰(除了当前的 Intellisense 问题),但它有一个主要缺点 - 需要访问EntityFrameworkQueryableExtensions,从而引用Microsoft.EntityFrameworkCore程序集。而paramsExpression>`模式则没有这样的要求。

好处是可以相对轻松地添加该功能。EF6 源代码在 GitHub 上公开可用,从那里我们可以看到它使用名为TryParsePath的方法来构建点分隔的字符串路径,然后将其传递给方法string的重载Include

同样的情况也适用于 EF Core。我们可能可以使用 EF6 代码,但我将提供我自己的版本。可以很容易地看出,支持的构造是成员访问器或对使用Select2 个参数调用的方法的调用,第二个参数是LambdaExpression

以下是我对上述内容的解释,封装在两个自定义扩展方法中:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Microsoft.EntityFrameworkCore
{
    public static class IncludeExtensions
    {
        public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> includePaths) where T : class
            => includePaths.Aggregate(source, (query, path) => query.Include(path));

        public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> includePaths) where T : class
            => source.Include(includePaths.Select(e => GetIncludePath(e?.Body)));

        static string GetIncludePath(Expression source, bool allowParameter = false)
        {
            if (allowParameter && source is ParameterExpression)
                return null; // ok
            if (source is MemberExpression member)
                return CombinePaths(GetIncludePath(member.Expression, true), member.Member.Name);
            if (source is MethodCallExpression call && call.Method.Name == "Select"
                && call.Arguments.Count == 2 && call.Arguments[1] is LambdaExpression selector)
                return CombinePaths(GetIncludePath(call.Arguments[0]), GetIncludePath(selector.Body));
            throw new Exception("Invalid Include path.");
        }

        static string CombinePaths(string path1, string path2)
            => path1 != null ? path1 + "." + path2 : path2;
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个只是用于调用多个string包含的帮助程序(取自我对Entity Framework Core 2.0.1 Eager Loading on allnested relatedEntity 的回答)。第二个是所讨论的方法,它将表达式转换为字符串并调用第一个。主要工作是通过GetIncludePath私有方法完成的,该方法根据上述规则递归地处理表达式,再加上一个附加规则 - 当自下而上导航时,它应该以 lambda 参数结束。

现在该方法的实现问题很简单:

public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
    => set.Include(includeProperties);
Run Code Online (Sandbox Code Playgroud)