简体   繁体   English

怎么样.ThenBy实施了吗?

[英]How is .ThenBy implemented?

Inspired by this I made this: 灵感来自于此我做了这个:

ISortable ISortable

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

IPageable 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; }
}

Pageable 分页

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
}

The above works. 以上工作。 Great success! 巨大的成功! :) :)

However I've run into a problem implementing the method .ThenBy() . 但是我遇到了实现该方法的问题.ThenBy() The problem is that .ThenBy() should only be accessible when .OrderBy() has been called. 问题是.ThenBy()只有在.OrderBy()时才能访问。

I noticed that IQueryable.OrderBy returns an IOrderedQueryable and that's where the access to .ThenBy() comes from. 我注意到IQueryable.OrderBy返回一个IOrderedQueryable,这是访问.ThenBy()地方。 But in order to make my current solution work I'd need to make a IOrderedPageable and a new OrderedPagable to go with it. 但是为了使我当前的解决方案工作,我需要制作一个IOrderedPageable和一个新的OrderedPagable。 The OrderedPagable would be an almost exact copy of Pageable which is really really bad design. OrderedPagable几乎就是Pageable的副本,这真的是非常糟糕的设计。

I highly doubt that's how it's done in LINQ. 我非常怀疑它是如何在LINQ中完成的。 So my question is, how did they do it? 所以我的问题是,他们是怎么做到的? I'm very curious :) 我很好奇 :)

One thing I did notice is that pretty much all the LINQ methods are extension methods, is that part of the "trick" :)? 我注意到的一件事是,几乎所有LINQ方法都是扩展方法,是“技巧”的一部分:)?

It sounds like your class OrderedPageable could be a subclass of Pageable and additionally implement the IOrderedPageable interface. 听起来您的类OrderedPageable可能是Pageable的子类,另外还实现了IOrderedPageable接口。

The inheritance approach would seem to make sense, since anything that handles Pageable should likely be able to handle OrderedPageable in the same way. 继承方法似乎有意义,因为处理Pageable任何东西都应该能够以相同的方式处理OrderedPageable


By slightly changing your interfaces and using an interface inheritance approach similar to the above, you can achieve the functionality you're looking for with immutable queryable classes and extension methods. 通过稍微更改接口并使用类似于上面的接口继承方法,您可以使用不可变的可查询类和扩展方法来实现您正在寻找的功能。

In my opinion, the usage is clearer and more consistent with LINQ. 在我看来,使用更清晰,更符合LINQ。

Examples: 例子:

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

I haven't tested this but the concept should work. 我没有测试过这个,但这个概念应该有效。 Note that: 注意:

  • the total item count is computed from the original query (not any of the sorted queries) 总项数是从原始查询计算的(不是任何已排序的查询)
  • the total item count is lazy and only computed once 总项数是懒惰的,只计算一次
  • depending on your query provider, you may have to expose a SourceQuery property of IPageableQuery for use within PageableExtensions , as your query provider may not successfully translate queries against this new PageableQuery type. 根据您的查询提供程序,您可能必须公开IPageableQuerySourceQuery属性以在PageableExtensions使用,因为您的查询提供程序可能无法针对此新的PageableQuery类型成功转换查询。

Interfaces: 接口:

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

    int PageSize { get; }
}

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

Implementations: 实现:

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; } }
}

Extension Methods: 扩展方法:

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);
    }
}

My suggested design for this would be making it explicit that your methods are in fact mutating the source object and deliberately not mirroring LINQ method names to avoid confusion. 我建议的设计是明确表示你的方法实际上是在改变源对象,故意不反映LINQ方法名称以避免混淆。 I omitted the IPageable interface and a bunch of other stuff for clarity, since the code is already a bit lengthy: 为了清楚起见,我省略了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();
    }
}

Note that the source query remains the same so you could change the parameters before enumerating the elements. 请注意,源查询保持不变,因此您可以在枚举元素之前更改参数。 This would also let you implement chaining behaviour if you wanted by returning a new Pageable based on the current one instead of this in the methods that do that, but it would make the code even clunkier since you'd have create a copy constructor and add code to create those derived objects. 这也可以让你实现链接行为,如果你想通过返回一个基于当前的Pageable而不是this做的方法来实现链接行为,但它会使代码更加笨拙,因为你已经创建了一个复制构造函数并添加用于创建这些派生对象的代码。

Also I believe the annoying redundant code in SortAndPage() could be refactored (or at least golfed down) using some FP approaches but it's probably more straightforward to read the way it is. 另外我相信SortAndPage()恼人的冗余代码可以使用一些FP方法进行重构(或至少高尔夫球化),但它可能更直接地阅读它的方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM