Свойства навигации EF Core исчезают при упорядочении по лямбде - PullRequest
0 голосов
/ 16 ноября 2018

У меня есть сущность Item, которая имеет отношение один ко многим к ItemVariant.Я пытаюсь упорядочить элементы по цене элемента ItemVariant, но свойство навигации ItemVariants (как и любое другое свойство навигации) пусто.Интересно, что он не пустой, прежде чем вводить порядок лямбды.Это работает, только если я выполняю ToListAsync перед упорядочиванием.

// entities I use
public class Item
{
    public int Id { get; set; }
    public string Title { get; set; }

    public ICollection<ItemVariant> ItemVariants { get; set; } = new List<ItemVariant>();
}

public class ItemVariant
{
    public int Id { get; set; }
    public int ItemId { get; set; }

    public Item Item { get; set; }
}

/// <summary>
/// Contains full information for executing a request on database
/// </summary>
/// <typeparam name="T"></typeparam>
public class Specification<T> where T : class
{
    public Expression<Func<T, bool>> Criteria { get; }
    public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
    public List<Func<T, IComparable>> OrderByValues { get; set; } = new List<Func<T, IComparable>>();
    public bool OrderByDesc { get; set; } = false;

    public int Take { get; protected set; }
    public int Skip { get; protected set; }
    public int Page => Skip / Take + 1;
    public virtual string Description { get; set; }
}

// retrieves entities according to specification passed
public static async Task<IEnumerable<TEntity>> EnumerateAsync<TEntity, TService>(this DbContext context, IAppLogger<TService> logger, Specification<TEntity> listSpec) where TEntity: class
{
    if (listSpec == null)
        throw new ArgumentNullException(nameof(listSpec));
    try
    {
        var entities = context.GetQueryBySpecWithIncludes(listSpec);
        var ordered = ApplyOrdering(entities, listSpec);
        var paged = await ApplySkipAndTake(ordered, listSpec).ToListAsync();
        return paged;
    }
    catch (Exception readException)
    {
        throw readException.LogAndGetDbException(logger, $"Function: {nameof(EnumerateAsync)}, {nameof(listSpec)}: {listSpec}");
    }
}

// applies Includes and Where to IQueryable. note that Include happens before OrderBy.
public static IQueryable<T> GetQueryBySpecWithIncludes<T>(this DbContext context, Specification<T> spec) where T: class
{
    // fetch a Queryable that includes all expression-based includes
    var queryableResultWithIncludes = spec.Includes
        .Aggregate(context.Set<T>().AsQueryable(),
            (current, include) => current.Include(include));
    var result = queryableResultWithIncludes;
    var filteredResult = result.Where(spec.Criteria);
    return filteredResult;
}

// paging
public static IQueryable<T> ApplySkipAndTake<T>(IQueryable<T> entities, Specification<T> spec) where T : class
{
    var result = entities;
    result = result.Skip(spec.Skip);
    return spec.Take > 0 ? result.Take(spec.Take) : result;
}

// orders IQueryable according to Lambdas in OrderByValues
public static IQueryable<T> ApplyOrdering<T>(IQueryable<T> entities, Specification<T> spec) where T : class
{
    // according to debugger all nav properties are loded at this point
    var result = entities;
    if (spec.OrderByValues.Count > 0)
    {
        var firstField = spec.OrderByValues.First();
        // but disappear when go into ordering lamda
        var orderedResult = spec.OrderByDesc ? result.OrderByDescending(i => firstField(i)) : result.OrderBy(i => firstField(i));
        foreach (var field in spec.OrderByValues.Skip(1))
            orderedResult = spec.OrderByDesc ? orderedResult.ThenByDescending(i => field(i)) : orderedResult.ThenBy(i => field(i));
        result = orderedResult;
    }
    return result;
}

это часть кода моего контроллера, применяющего упорядочение.он вызывается до EnumerateAsync

protected override void ApplyOrdering(Specification<Item> spec)
{
    spec.AddInclude(i => i.ItemVariants);
    spec.OrderByValues.Add(i =>
    {
        // empty if ToListAsync() not called before
        if (i.ItemVariants.Any())
            return (from v in i.ItemVariants select v.Price).Min();
        return 0;
    });
}

Вызов ToListAsync до подкачки не является оптимальным, потому что это означает, что загрузка гораздо большего количества объектов, чем необходимо, из-за еще не примененного подкачки (результаты подкачки также зависят от порядка).Может быть, есть какая-то конфигурация для загрузки свойств nav при необходимости?

Обновление : попытался использовать .UseLazyLoadingProxies(), но при ItemVariants.Any() я получаю исключение и неuse AsNoTracking().

Ошибка, сгенерированная для предупреждения 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning: была предпринята попытка лениво загрузить свойство навигации ItemVariants на отдельную сущность типа ItemProxy.Ленивая загрузка не поддерживается для отдельных объектов или объектов, которые загружены с помощью «AsNoTracking ()».Это исключение можно подавить или зарегистрировать, передав идентификатор события «CoreEventId.DetachedLazyLoadingWarning» методу «ConfigureWarnings» в «DbContext.OnConfiguring» или «AddDbContext». '

1 Ответ

0 голосов
/ 18 ноября 2018

Основной причиной проблемы является использование делегата (Func<T, IComparable>) для заказа вместо Expression<Func<...>>.

EF6 просто выдаст NotSupportedException во время выполнения, но EF Core переключится на оценка клиента .

Помимо введенной неэффективности, оценка клиента в настоящее время плохо сочетается со свойствами навигации - похоже, что она применяется * до стремление исправить свойства загрузки / навигации, поэтому свойство навигации имеет значение null.

Даже если реализация EF Core исправлена ​​на «работать» в каком-либо будущем выпуске, в общем, вам следует избегать оценки клиента, когда это возможно.Это означает, что часть упорядочения используемой реализации шаблона спецификации должна быть скорректирована для работы с выражениями, чтобы иметь возможность создавать что-то вроде этого

.OrderBy(i => i.ItemVariants.Max(v => (decimal?)v.Price))

, которое должно быть переведено в SQL, а следовательно, оцененона стороне сервера и никаких проблем со свойствами навигации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...