我有一个扩展方法来对我的实体进行排序,在某些情况下我需要对子集合的属性进行排序
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为单位的实体加载到内存中,然后进行排序)。
正确的方法是什么?
答案 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)