【问题标题】:Ordering By Child Entity EF Core按子实体 EF Core 排序
【发布时间】:2018-04-05 08:11:54
【问题描述】:

我有一个扩展方法来对我的实体进行排序,在某些情况下我需要对子集合的属性进行排序

 public static IQueryable<Payment> SetSort(this IQueryable<Payment> payments, string sortProperty, string direction)


            if (string.Equals(sortProperty, PaymentSortProperties.TimeStamp, StringComparison.CurrentCultureIgnoreCase))
            {
                return sortDirection == SortDirection.Asc ? payments.OrderBy(x => x.History.OrderBy(h=> h.Timestamp)) : payments.OrderByDescending(x => x.History.OrderByDescending(h => h.Timestamp));
            }

}

调用
 public async Task<IPagedList<Payment>> Get(int pageNumber, int pageSize, string sortProperty, string direction, string searchString)
    {
            var result = _data.Payments
                .Include(x => x.History)
                .ThenInclude(x=>x.Status)
                .Filter(searchString)
            .SetSort(sortProperty, direction);

            return await result.ToPagedListAsync(pageNumber, pageSize);         
    }

我收到错误System.ArgumentException: At least one object must implement IComparable.

我见过一些例子表明我会这样做

    if (string.Equals(sortProperty, PaymentSortProperties.TimeStamp, StringComparison.CurrentCultureIgnoreCase))
                {
                    return sortDirection == SortDirection.Asc ?
 payments.OrderBy(x => x.History.Min(h=> h.Timestamp)) 
: payments.OrderByDescending(x => x.History.Max(h => h.Timestamp));
                }

但这会触发SELECT n + 1 查询(即导致将dB 中的所有实体加载到内存中,然后进行排序)。

正确的做法是什么?

【问题讨论】:

  • 有更好的方法来动态添加排序表达式,这里只是众多示例之一:stackoverflow.com/a/36303246/861716。但这并不能解决您的直接问题,即(可能)您正在尝试输入一个带点的名称。您没有显示您尝试排序的属性,所以我们必须猜测。
  • 我试图排序的属性是 History 集合上的 Timestamp,它位于第一个代码块中。
  • 啊,是的。根据x =&gt; x.History.OrderBy(h=&gt; h.Timestamp) 的结果排序应该是什么样子的?
  • 付款按历史集合中最早的时间戳排序(使用 OrderBy 时)
  • 您正在尝试订购收藏品就是我的意思。仔细看看你的代码做了什么。

标签: c# .net entity-framework entity-framework-core ef-core-2.0


【解决方案1】:

嗯,Min / Max 通常是正确的方法。不幸的是,正如您所注意到的,EF Core(从 v2.0 开始)仍然不能很好地转换 (GroupBy) 聚合方法,并回退到客户端评估来处理它们。

作为一种解决方法,我可以建议替代模式 OrderBy[Descending] + Select + FirstOrDefault 幸运地转换为 SQL:

return sortDirection == SortDirection.Asc ?
    payments.OrderBy(p => p.History.OrderBy(h => h.Timestamp).Select(h => h.Timestamp).FirstOrDefault()) :
    payments.OrderByDescending(x => x.History.OrderByDescending(h => h.Timestamp).Select(h => h.Timestamp).FirstOrDefault());

这里同样封装在一个自定义扩展方法中:

public static class QueryableExtensions
{
    public static IOrderedQueryable<TOuter> OrderBy<TOuter, TInner, TKey>(
        this IQueryable<TOuter> source,
        Expression<Func<TOuter, IEnumerable<TInner>>> innerCollectionSelector,
        Expression<Func<TInner, TKey>> keySelector,
        bool ascending)
    {
        return source.OrderBy(innerCollectionSelector, keySelector, ascending, false);
    }

    public static IOrderedQueryable<TOuter> ThenBy<TOuter, TInner, TKey>(
        this IOrderedQueryable<TOuter> source,
        Expression<Func<TOuter, IEnumerable<TInner>>> innerCollectionSelector,
        Expression<Func<TInner, TKey>> keySelector,
        bool ascending)
    {
        return source.OrderBy(innerCollectionSelector, keySelector, ascending, true);
    }

    static IOrderedQueryable<TOuter> OrderBy<TOuter, TInner, TKey>(
        this IQueryable<TOuter> source,
        Expression<Func<TOuter, IEnumerable<TInner>>> innerCollectionSelector,
        Expression<Func<TInner, TKey>> innerKeySelector,
        bool ascending, bool concat)
    {
        var parameter = innerCollectionSelector.Parameters[0];
        var innerOrderByMethod = ascending ? "OrderBy" : "OrderByDescending";
        var innerOrderByCall = Expression.Call(
            typeof(Enumerable), innerOrderByMethod, new[] { typeof(TInner), typeof(TKey) },
            innerCollectionSelector.Body, innerKeySelector);
        var innerSelectCall = Expression.Call(
            typeof(Enumerable), "Select", new[] { typeof(TInner), typeof(TKey) },
            innerOrderByCall, innerKeySelector);
        var innerFirstOrDefaultCall = Expression.Call(
            typeof(Enumerable), "FirstOrDefault", new[] { typeof(TKey) },
            innerSelectCall);
        var outerKeySelector = Expression.Lambda(innerFirstOrDefaultCall, parameter);
        var outerOrderByMethod = concat ? ascending ? "ThenBy" : "ThenByDescending" : innerOrderByMethod;
        var outerOrderByCall = Expression.Call(
            typeof(Queryable), outerOrderByMethod, new[] { typeof(TOuter), typeof(TKey) },
            source.Expression, Expression.Quote(outerKeySelector));
        return (IOrderedQueryable<TOuter>)source.Provider.CreateQuery(outerOrderByCall);
    }
}

所以你可以简单地使用:

return payments.OrderBy(p => p.History, h => h.Timestamp, sortDirection == SortDirection.Asc)

【讨论】:

  • 谢谢,我明天试试
猜你喜欢
  • 1970-01-01
  • 2023-02-04
  • 2015-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 1970-01-01
  • 2020-04-09
相关资源
最近更新 更多