将通用急切加载方法从 EF6 转换为 EF Core [英] Translating generic eager load method from EF6 to EF Core

查看:50
本文介绍了将通用急切加载方法从 EF6 转换为 EF Core的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于 EF6,我在通用存储库中有一个方法,我向所有服务层公开该方法,以便根据需要从数据库中检索具有任何嵌套属性的实体:

For EF6, I had a method in my generic repository that I exposed to all service layers in order to retrieve entities from the database with any nested properties as needed:

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

    return includeProperties.Aggregate(queryable, (current, includeProperty) => current.Include(includeProperty));
}

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

This way, I could use the method in the following way:

var data = repo.OldMethod(x => x.Papers, => x.People.Select(y => y.Addresses)).ToList();

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

In EF6, this would load the Papers navigation property, the People navigation property, and the Addresses navigation property on each person. This, as expected, throws an exception in EFCore. Because of the switch to Include-->ThenInclude method in EFCore, I'm not quite sure how to easily replicate this at my service layer which I'd like to not require any information about EntityFramework.

推荐答案

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

This has been asked many times since the initial release of EF Core. Earlier prerelease versions of EF Core even were supporting it, but then it has been removed from EF Core code (I guess in order to promote the new Include / ThenInclude pattern).

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

While Include / ThenInclude pattern looks more clear (besides the current Intellisense issues), it has one major drawback - requires access to EntityFrameworkQueryableExtensions, thus reference to Microsoft.EntityFrameworkCore assembly. While paramsExpression>` pattern has no such requirement.

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

The good thing is the one can relatively easily add that functionality. The EF6 source code is publicly available on GitHub, and from there we can see that it uses a method called TryParsePath to build dot separated string path which then is passed to the string overload of Include method.

同样可以应用于 EF Core.我们可能可以使用 EF6 代码,但我将提供我自己的版本.很容易看出,支持的构造是成员访问器或对名为 Select 的方法的调用,带有 2 个参数,第二个参数是 LambdaExpression.

The same can be applied in EF Core. We can probably use the EF6 code, but I'm going to provide my own version. It can be easily be seen that the supported constructs are member accessors or calls to method called Select with 2 arguments, the second being LambdaExpression.

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

Following is my interpretation of the above, encapsulated in two custom extension methods:

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

第一个只是调用多个 string 包含的帮助程序(取自我对 Entity Framework Core 2.0.1 预加载所有嵌套相关实体).第二个是有问题的方法,它将表达式转换为字符串并调用第一个.主要工作由 GetIncludePath 私有方法完成,该方法根据上述规则递归处理表达式,再加上一个附加规则 - 自下而上导航时,它应该以 lambda 参数结束.

The first is simply helper for calling multiple string includes (taken from my answer to Entity Framework Core 2.0.1 Eager Loading on all nested related entities). The second is the method in question, which converts the expressions to strings and call the first. The main work is done by GetIncludePath private method which recursively processes the expression based on the aforementioned rules, plus one additional rule - when navigating bottom up, it should end with lambda parameter.

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

Now the implementation of the method is question is simple as that:

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

这篇关于将通用急切加载方法从 EF6 转换为 EF Core的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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