Перевод общего метода активной загрузки из EF6 в EF Core - PullRequest
0 голосов
/ 25 октября 2018

Для 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.Из-за переключения метода Include -> ThenInclude в EFCore я не совсем уверен, как легко воспроизвести это на моем уровне обслуживания, который не требует никакой информации о EntityFramework.

Ответы [ 2 ]

0 голосов
/ 29 октября 2018

Об этом много раз спрашивали с момента первого выпуска EF Core.Более ранние предварительные версии EF Core даже поддерживали его, но затем он был удален из кода EF Core (я думаю, чтобы продвигать новый шаблон Include / ThenInclude).

Пока Include/ ThenInclude шаблон выглядит более четким (помимо текущих проблем с Intellisense), у него есть один существенный недостаток - требуется доступ к EntityFrameworkQueryableExtensions, то есть ссылка на Microsoft.EntityFrameworkCore сборку.В то время как params Expression> `pattern не имеет такого требования.

Хорошо, что можно относительно легко добавить эту функциональность.Исходный код EF6 общедоступен на GitHub, и оттуда мы видим, что он использует метод с именем TryParsePath для построения пути строки, разделенного точками, который затем передается перегрузке string метода Include.

То же самое можно применить в EF Core.Возможно, мы можем использовать код EF6, но я собираюсь предоставить свою собственную версию.Легко видеть, что поддерживаемые конструкции являются членами-членами или обращаются к методу, называемому 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;
    }
}

Первый - просто помощник для вызова нескольких включений string (взято из моего ответа на Entity Framework Core 2.0.1 Eager Загрузка всех вложенных связанных сущностей ).Второй - это рассматриваемый метод, который преобразует выражения в строки и вызывает первый.Основная работа выполняется GetIncludePath приватным методом, который рекурсивно обрабатывает выражение на основе вышеупомянутых правил, плюс одно дополнительное правило - при переходе снизу вверх он должен заканчиваться параметром lambda.

Теперь реализацияМетод - это простой вопрос:

public IQueryable<T> OldMethod(params Expression<Func<T, object>>[] includeProperties)
    => set.Include(includeProperties);
0 голосов
/ 29 октября 2018

Когда я впервые начал использовать 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()));
}
...