将通用的紧急加载方法从EF6转换为EF Core

时间:2018-10-25 19:53:08

标签: c# entity-framework .net-core entity-framework-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));
}

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

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

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

2 个答案:

答案 0 :(得分:3)

自EF Core最初发布以来,已经多次询问了此问题。早期的EF Core发行版甚至都支持它,但是后来它从EF Core代码中删除了(我想是为了推广新的Include / ThenInclude模式)。

尽管Include / ThenInclude模式看起来更清晰(除了当前的Intellisense问题),它还有一个主要缺点-需要访问EntityFrameworkQueryableExtensions,因此引用了Microsoft.EntityFrameworkCore部件。 params Expression>`模式没有此要求。

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

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

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

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; } } 包含项的助手(摘自我对Entity Framework Core 2.0.1 Eager Loading on all nested related entities的回答)。第二个是有问题的方法,该方法将表达式转换为字符串并调用第一个。主要工作是通过string私有方法完成的,该方法根据上述规则以及一个附加规则递归处理表达式-在自下而上导航时,应以lambda参数结尾。

现在该方法的实现很简单,因为问题是这样的:

GetIncludePath

答案 1 :(得分:1)

当我第一次开始使用EFCore(从EF6切换)时,我构建了这些扩展方法,将包括x => x.People.Select(y => y.Addresses)在内的“旧”方式转换为"People.Addresses"之类的字符串,EFCore也支持这些字符串;

public static class Extensions
{

    private class ReferencedPropertyFinder : ExpressionVisitor
    {
        private readonly Type _ownerType;
        private readonly List<PropertyInfo> _properties = new List<PropertyInfo>();
        private Expression _parameterExpression;
        private int _currentPosition = 0;

        public ReferencedPropertyFinder(Type ownerType)
        {
            _ownerType = ownerType;
        }

        public IReadOnlyList<PropertyInfo> Properties
        {
            get { return _properties; }
        }

        protected override Expression VisitMember(MemberExpression node)
        {

            var propertyInfo = node.Member as PropertyInfo;
            if (propertyInfo != null) {
                var currentParameter = GetParameter(node);

                if (_parameterExpression == currentParameter) {
                    _properties.Insert(_currentPosition, propertyInfo);
                } else {
                    _properties.Add(propertyInfo);
                    _parameterExpression = currentParameter;
                    _currentPosition = _properties.Count() - 1;
                }

            }

            return base.VisitMember(node);
        }

        private ParameterExpression GetParameter(MemberExpression node)
        {
            if (node.Expression is ParameterExpression) {
                return (ParameterExpression)node.Expression;
            } else {
                return GetParameter((MemberExpression)node.Expression);
            }
        }


    }


    private static IReadOnlyList<PropertyInfo> GetReferencedProperties<T, U>(this Expression<Func<T, U>> expression)
    {
        var v = new ReferencedPropertyFinder(typeof(T));
        v.Visit(expression);
        return v.Properties;
    }

    public static string ToPropertyPath<T>(this Expression<Func<T, object>> expression)
    {
        var properties = expression.GetReferencedProperties();
        var path = string.Join(".", properties.Select(x => x.Name));
        return path;
    }

}

将这些内容合并到您的代码中,您可以这样说:

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

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