簡體   English   中英

怎么樣.ThenBy實施了嗎?

[英]How is .ThenBy implemented?

靈感來自於此我做了這個:

ISortable

public interface ISortable<T>
{
    IPageable<T> OrderBy<U>(Expression<Func<T, U>> orderBy);
    IPageable<T> OrderByDescending<U>(Expression<Func<T, U>> orderBy);
}

IPageable

public interface IPageable<T> : ISortable<T>, IEnumerable<T>
{
    IPageable<T> Page(int pageNumber, int pageSize);
    List<T> ToList();
    int TotalPages { get; }
    int TotalItemCount { get; }
    int PageNumber { get; }
    int? PageSize { get; }
}

分頁

public class Pageable<T> : IPageable<T>
{
    private readonly IQueryable<T> _countQuery;
    private IQueryable<T> _sourceQuery;

    /// <summary>
    /// A pageable result
    /// </summary>
    /// <param name="sourceQuery">Query which holdes all relevant items.</param>
    public Pageable(IQueryable<T> sourceQuery)
    {
        _sourceQuery = sourceQuery;
        _countQuery = sourceQuery;
        PageNumber = 1;
    }

    /// <summary>
    /// A pageable result
    /// </summary>
    /// <param name="sourceQuery">Query which holdes all relevant items.</param>
    /// <param name="countQuery">
    /// Alternative query optimized for counting.
    /// <see cref="countQuery"/> is required to give the same count as <see cref="sourceQuery"/> else paging will break. 
    /// <remarks>No checks if <see cref="sourceQuery"/> and <see cref="countQuery"/> return the same count are appiled.</remarks>
    /// </param>
    public Pageable(IQueryable<T> sourceQuery, IQueryable<T> countQuery)
        : this (sourceQuery)
    {
        _countQuery = countQuery;
    }

    #region Implementation of IEnumerable

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
    /// </returns>
    /// <filterpriority>1</filterpriority>
    public IEnumerator<T> GetEnumerator()
    {
        return _sourceQuery.GetEnumerator();
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
    /// </returns>
    /// <filterpriority>2</filterpriority>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    #region Implementation of ISortable

    public IPageable<T> OrderBy<U>(Expression<Func<T, U>> orderBy)
    {
        _sourceQuery = _sourceQuery.OrderBy(orderBy);
        return this;
    }

    public IPageable<T> OrderByDescending<U>(Expression<Func<T, U>> orderBy)
    {
        _sourceQuery = _sourceQuery.OrderByDescending(orderBy);
        return this;
    }

    #endregion

    #region Implementation of IPageable

    public int PageNumber { get; private set; }
    public int? PageSize { get; private set; }
    public int TotalItemCount
    {
        get { return _countQuery.Count(); }
    }
    public int TotalPages
    {
        get { return (int) (Math.Ceiling((double) TotalItemCount/PageSize ?? 1)); }
    }

    /// <summary>
    /// Chop a query result into pages.
    /// </summary>
    /// <param name="pageNumber">Page number to fetch. Starting from 1.</param>
    /// <param name="pageSize">Items per page.</param>
    /// <returns></returns>
    public IPageable<T> Page(int pageNumber, int pageSize)
    {
        PageNumber = pageNumber;
        PageSize = pageSize;

        _sourceQuery = _sourceQuery
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize);

        return this;
    }

    public List<T> ToList()
    {
        return _sourceQuery.ToList();
    }

    #endregion
}

以上工作。 巨大的成功! :)

但是我遇到了實現該方法的問題.ThenBy() 問題是.ThenBy()只有在.OrderBy()時才能訪問。

我注意到IQueryable.OrderBy返回一個IOrderedQueryable,這是訪問.ThenBy()地方。 但是為了使我當前的解決方案工作,我需要制作一個IOrderedPageable和一個新的OrderedPagable。 OrderedPagable幾乎就是Pageable的副本,這真的是非常糟糕的設計。

我非常懷疑它是如何在LINQ中完成的。 所以我的問題是,他們是怎么做到的? 我很好奇 :)

我注意到的一件事是,幾乎所有LINQ方法都是擴展方法,是“技巧”的一部分:)?

聽起來您的類OrderedPageable可能是Pageable的子類,另外還實現了IOrderedPageable接口。

繼承方法似乎有意義,因為處理Pageable任何東西都應該能夠以相同的方式處理OrderedPageable


通過稍微更改接口並使用類似於上面的接口繼承方法,您可以使用不可變的可查詢類和擴展方法來實現您正在尋找的功能。

在我看來,使用更清晰,更符合LINQ。

例子:

query.AsPageable(100).Page(1);
query.AsPageable(100).OrderBy(x => x.Name).ThenBy(x => x.Age).Page(1).ToList();

我沒有測試過這個,但這個概念應該有效。 注意:

  • 總項數是從原始查詢計算的(不是任何已排序的查詢)
  • 總項數是懶惰的,只計算一次
  • 根據您的查詢提供程序,您可能必須公開IPageableQuerySourceQuery屬性以在PageableExtensions使用,因為您的查詢提供程序可能無法針對此新的PageableQuery類型成功轉換查詢。

接口:

public interface IPageableQuery<T> : IQueryable<T>
{
    int TotalPages { get; }
    int TotalItemCount { get; }

    int PageSize { get; }
}

public interface IOrderedPageableQuery<T> : IPageableQuery<T>, IOrderedQueryable<T>
{
}

實現:

public class PageableQuery<T> : IPageableQuery<T>
{
    readonly IQueryable<T> _sourceQuery;
    readonly Lazy<int> _totalItemCount; 

    public int TotalPages { get { return (int)Math.Ceiling((double)TotalItemCount / PageSize); } }
    public int TotalItemCount { get { return _totalItemCount.Value; } }
    public int PageSize { get; private set; }

    public PageableQuery(IQueryable<T> sourceQuery, int pageSize)
    {
        _sourceQuery = sourceQuery;
        _totalItemCount = new Lazy<int>(() => _sourceQuery.Count());

        PageSize = pageSize;
    }

    public IEnumerator<T> GetEnumerator() { return _sourceQuery.GetEnumerator();}
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

    public Expression Expression { get { return _sourceQuery.Expression; } }
    public Type ElementType { get { return _sourceQuery.ElementType; } }
    public IQueryProvider Provider { get { return _sourceQuery.Provider; } }
}

public class OrderedPageableQuery<T> : IOrderedPageableQuery<T>
{
    readonly IPageableQuery<T> _sourcePageableQuery;
    readonly IOrderedQueryable<T> _sourceQuery;

    public int TotalPages { get { return (int)Math.Ceiling((double)TotalItemCount / PageSize); } }
    public int TotalItemCount { get { return _sourcePageableQuery.TotalItemCount; } }
    public int PageSize { get { return _sourcePageableQuery.PageSize; } }

    public OrderedPageableQuery(IPageableQuery<T> sourcePageableQuery, IOrderedQueryable<T> newSourceQuery)
    {
        _sourcePageableQuery = sourcePageableQuery;
        _sourceQuery = newSourceQuery;
    }

    public IEnumerator<T> GetEnumerator() { return _sourceQuery.GetEnumerator();}
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

    public Expression Expression { get { return _sourceQuery.Expression; } }
    public Type ElementType { get { return _sourceQuery.ElementType; } }
    public IQueryProvider Provider { get { return _sourceQuery.Provider; } }
}

擴展方法:

public static class PageableExtension
{
    public static IPageableQuery<T> AsPageable<T>(this IQueryable<T> sourceQuery, int pageSize)
    {
        return new PageableQuery<T>(sourceQuery, pageSize);
    }

    public static IOrderedPageableQuery<T> OrderBy<T, U>(this IPageableQuery<T> sourcePageableQuery, Expression<Func<T, U>> orderBy)
    {
        return new OrderedPageableQuery<T>(sourcePageableQuery, Queryable.OrderBy(sourcePageableQuery, orderBy));
    }

    public static IOrderedPageableQuery<T> OrderByDescending<T, U>(this IPageableQuery<T> sourcePageableQuery, Expression<Func<T, U>> orderBy)
    {
        return new OrderedPageableQuery<T>(sourcePageableQuery, Queryable.OrderByDescending(sourcePageableQuery, orderBy));
    }

    public static IOrderedPageableQuery<T> ThenBy<T, U>(this IOrderedPageableQuery<T> sourcePageableQuery, Expression<Func<T, U>> orderBy)
    {
        return new OrderedPageableQuery<T>(sourcePageableQuery, Queryable.ThenBy(sourcePageableQuery, orderBy));
    }

    public static IOrderedPageableQuery<T> ThenByDescending<T, U>(this IOrderedPageableQuery<T> sourcePageableQuery, Expression<Func<T, U>> orderBy)
    {
        return new OrderedPageableQuery<T>(sourcePageableQuery, Queryable.ThenByDescending(sourcePageableQuery, orderBy));
    }

    public static IQueryable<T> Page<T>(this IPageableQuery<T> sourceQuery, int pageNumber)
    {
        return sourceQuery.Skip((pageNumber - 1) * sourceQuery.PageSize)
                          .Take(sourceQuery.PageSize);
    }
}

我建議的設計是明確表示你的方法實際上是在改變源對象,故意不反映LINQ方法名稱以避免混淆。 為了清楚起見,我省略了IPageable接口和一堆其他東西,因為代碼已經有點冗長了:

public interface ISortable<T>
{
    Pageable<T> ResetOrder();
    Pageable<T> AddOrder(Expression<Func<T, object>> orderBy);
    Pageable<T> AddOrderDescending(Expression<Func<T, object>> orderBy);
}

public class Pageable<T> : ISortable<T>, IEnumerable<T> {
    class SortKey {
        public Expression<Func<T, object>> Expression { get; set; }
        public bool Descending { get; set; }
    }

    List<SortKey> _sortKeys = new List<SortKey>();

    System.Linq.IQueryable<T> _sourceQuery;

    int _pageNumber;
    int _pageSize;

    public Pageable<T> SetPage(int pageNumber, int pageSize) {
        _pageNumber = pageNumber;
        _pageSize = pageSize;
        return this;
    }

    public Pageable<T> ResetOrder()
    {
        _sortKeys.Clear();
        return this;
    }

    public Pageable<T> AddOrder(Expression<Func<T, object>> orderBy)
    {
        _sortKeys.Add(new SortKey {
            Expression = orderBy, 
            Descending = false
        });
        return this;
    }

    public Pageable<T> AddOrderDescending(Expression<Func<T, object>> orderBy)
    {
        _sortKeys.Add(new SortKey {
            Expression = orderBy, 
            Descending = true
        });
        return this;
    }

    IEnumerable<T> SortAndPage()
    {
        if (_sortKeys.Count == 0) 
        {
            return Page(_sourceQuery);
        }

        var firstKey = _sortKeys[0];
        var orderedQuery = firstKey.Descending 
            ? _sourceQuery.OrderByDescending(firstKey.Expression) 
            : _sourceQuery.OrderBy(firstKey.Expression);

        foreach (var key in _sortKeys.Skip(1)) 
        {
            orderedQuery = key.Descending ? orderedQuery.ThenByDescending(key.Expression) : orderedQuery.ThenBy(key.Expression);
        }
        return Page(orderedQuery);
    }

    IEnumerable<T> Page(IQueryable<T> query) 
    {
        return query.Skip((_pageNumber - 1) * _pageSize)
                    .Take (_pageSize);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return SortAndPage().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

請注意,源查詢保持不變,因此您可以在枚舉元素之前更改參數。 這也可以讓你實現鏈接行為,如果你想通過返回一個基於當前的Pageable而不是this做的方法來實現鏈接行為,但它會使代碼更加笨拙,因為你已經創建了一個復制構造函數並添加用於創建這些派生對象的代碼。

另外我相信SortAndPage()惱人的冗余代碼可以使用一些FP方法進行重構(或至少高爾夫球化),但它可能更直接地閱讀它的方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM