使用 LINQ 对对象进行分页

问题描述 投票:0回答:14

如何在 LINQ 查询中实现分页? 其实暂时来说,如果能模仿sql TOP函数我就很满足了。不过,我确信无论如何,对完整分页支持的需求迟早会出现。

var queryResult = from o in objects
                  where ...
                  select new
                      {
                         A = o.a,
                         B = o.b
                      }
                   ????????? TOP 10????????
c# .net linq paging
14个回答
286
投票

您正在寻找

Skip
Take
扩展方法。
Skip
移过结果中的前 N 个元素,返回余数;
Take
返回结果中的前 N 个元素,删除所有剩余元素。

有关如何使用这些方法的更多信息,请参阅 Microsoft 文档:
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/return-or-skip-elements-in-a-sequence

如果你的 pageNumber 从 0 开始(按照评论中的建议每 1 减少),你可以这样做:

int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
  .Skip(numberOfObjectsPerPage * pageNumber)
  .Take(numberOfObjectsPerPage);

如果 pageNumber 从 1 开始(按照@Alvin 的建议),那么你可以像这样:

int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
  .Skip(numberOfObjectsPerPage * (pageNumber - 1))
  .Take(numberOfObjectsPerPage);

66
投票

使用

Skip
Take
绝对是正确的选择。如果我要实现这个,我可能会编写自己的扩展方法来处理分页(以使代码更具可读性)。实现当然可以使用
Skip
Take
:

static class PagingUtils {
  public static IEnumerable<T> Page<T>(this IEnumerable<T> en, int pageSize, int page) {
    return en.Skip(page * pageSize).Take(pageSize);
  }
  public static IQueryable<T> Page<T>(this IQueryable<T> en, int pageSize, int page) {
    return en.Skip(page * pageSize).Take(pageSize);
  }
}

该类定义了两种扩展方法 - 一种用于

IEnumerable
,另一种用于
IQueryable
,这意味着您可以将其与 LINQ to Objects 和 LINQ to SQL 一起使用(在编写数据库查询时,编译器将选择
IQueryable
版本)。

根据您的分页要求,您还可以添加一些额外的行为(例如处理负值

pageSize
page
值)。以下是如何在查询中使用此扩展方法的示例:

var q = (from p in products
         where p.Show == true
         select new { p.Name }).Page(10, pageIndex);

50
投票

这是我使用 LINQ to 对象时的高性能分页方法:

public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int pageSize)
{
    Contract.Requires(source != null);
    Contract.Requires(pageSize > 0);
    Contract.Ensures(Contract.Result<IEnumerable<IEnumerable<T>>>() != null);

    using (var enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            var currentPage = new List<T>(pageSize)
            {
                enumerator.Current
            };

            while (currentPage.Count < pageSize && enumerator.MoveNext())
            {
                currentPage.Add(enumerator.Current);
            }
            yield return new ReadOnlyCollection<T>(currentPage);
        }
    }
}

然后可以像这样使用:

var items = Enumerable.Range(0, 12);

foreach(var page in items.Page(3))
{
    // Do something with each page
    foreach(var item in page)
    {
        // Do something with the item in the current page       
    }
}

没有这些垃圾

Skip
Take
,如果您对多个页面感兴趣,这将是非常低效的。


11
投票
   ( for o in objects
    where ...
    select new
   {
     A=o.a,
     B=o.b
   })
.Skip((page-1)*pageSize)
.Take(pageSize)

7
投票

不知道这是否会对任何人有帮助,但我发现它对我的目的很有用:

private static IEnumerable<T> PagedIterator<T>(IEnumerable<T> objectList, int PageSize)
{
    var page = 0;
    var recordCount = objectList.Count();
    var pageCount = (int)((recordCount + PageSize)/PageSize);

    if (recordCount < 1)
    {
        yield break;
    }

    while (page < pageCount)
    {
        var pageData = objectList.Skip(PageSize*page).Take(PageSize).ToList();

        foreach (var rd in pageData)
        {
            yield return rd;
        }
        page++;
    }
}

要使用它,您需要一些 linq 查询,并将结果与页面大小一起传递到 foreach 循环中:

var results = from a in dbContext.Authors
              where a.PublishDate > someDate
              orderby a.Publisher
              select a;

foreach(var author in PagedIterator(results, 100))
{
    // Do Stuff
}

因此,这将迭代每个作者,一次获取 100 位作者。


5
投票
var queryResult = (from o in objects where ...
    select new {
        A = o.a,
        B = o.b
    }).Take(10);

4
投票

类似于 Lukazoid 的答案我已经为 IQueryable 创建了一个扩展。

   public static IEnumerable<IEnumerable<T>> PageIterator<T>(this IQueryable<T> source, int pageSize)
            {
                Contract.Requires(source != null);
                Contract.Requires(pageSize > 0);
                Contract.Ensures(Contract.Result<IEnumerable<IQueryable<T>>>() != null);

                using (var enumerator = source.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        var currentPage = new List<T>(pageSize)
                        {
                            enumerator.Current
                        };

                        while (currentPage.Count < pageSize && enumerator.MoveNext())
                        {
                            currentPage.Add(enumerator.Current);
                        }
                        yield return new ReadOnlyCollection<T>(currentPage);
                    }
                }
            }

如果不支持 Skip 或 Take ,这很有用。


3
投票
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);

Batchsize 显然是一个整数。这利用了整数简单地删除小数位的事实。

我对这个回应是半开玩笑的,但它会做你想做的事,而且因为它被推迟了,所以如果你这样做,你不会遭受很大的性能损失

pages.First(p => p.Key == thePage)

这个解决方案不适用于 LinqToEntities,我什至不知道它是否可以将其变成一个好的查询。


1
投票

我使用这个扩展方法:

public static IQueryable<T> Page<T, TResult>(this IQueryable<T> obj, int page, int pageSize, System.Linq.Expressions.Expression<Func<T, TResult>> keySelector, bool asc, out int rowsCount)
{
    rowsCount = obj.Count();
    int innerRows = rowsCount - (page * pageSize);
    if (innerRows < 0)
    {
        innerRows = 0;
    }
    if (asc)
        return obj.OrderByDescending(keySelector).Take(innerRows).OrderBy(keySelector).Take(pageSize).AsQueryable();
    else
        return obj.OrderBy(keySelector).Take(innerRows).OrderByDescending(keySelector).Take(pageSize).AsQueryable();
}

public IEnumerable<Data> GetAll(int RowIndex, int PageSize, string SortExpression)
{
    int totalRows;
    int pageIndex = RowIndex / PageSize;

    List<Data> data= new List<Data>();
    IEnumerable<Data> dataPage;

    bool asc = !SortExpression.Contains("DESC");
    switch (SortExpression.Split(' ')[0])
    {
        case "ColumnName":
            dataPage = DataContext.Data.Page(pageIndex, PageSize, p => p.ColumnName, asc, out totalRows);
            break;
        default:
            dataPage = DataContext.vwClientDetails1s.Page(pageIndex, PageSize, p => p.IdColumn, asc, out totalRows);
            break;
    }

    foreach (var d in dataPage)
    {
        clients.Add(d);
    }

    return data;
}
public int CountAll()
{
    return DataContext.Data.Count();
}

1
投票

有两个主要选项:

.NET >= 4.0 动态 LINQ:

  1. 使用System.Linq.Dynamic添加;在顶部。
  2. 用途:
    var people = people.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();

您也可以通过NuGet获取它。

.NET < 4.0 扩展方法:

private static readonly Hashtable accessors = new Hashtable();

private static readonly Hashtable callSites = new Hashtable();

private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(string name) {
    var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
    if(callSite == null)
    {
        callSites[name] = callSite = CallSite<Func<CallSite, object, object>>.Create(
                    Binder.GetMember(CSharpBinderFlags.None, name, typeof(AccessorCache),
                new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
    }
    return callSite;
}

internal static Func<dynamic,object> GetAccessor(string name)
{
    Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
    if (accessor == null)
    {
        lock (accessors )
        {
            accessor = (Func<dynamic, object>)accessors[name];
            if (accessor == null)
            {
                if(name.IndexOf('.') >= 0) {
                    string[] props = name.Split('.');
                    CallSite<Func<CallSite, object, object>>[] arr = Array.ConvertAll(props, GetCallSiteLocked);
                    accessor = target =>
                    {
                        object val = (object)target;
                        for (int i = 0; i < arr.Length; i++)
                        {
                            var cs = arr[i];
                            val = cs.Target(cs, val);
                        }
                        return val;
                    };
                } else {
                    var callSite = GetCallSiteLocked(name);
                    accessor = target =>
                    {
                        return callSite.Target(callSite, (object)target);
                    };
                }
                accessors[name] = accessor;
            }
        }
    }
    return accessor;
}
public static IOrderedEnumerable<dynamic> OrderBy(this IEnumerable<dynamic> source, string property)
{
    return Enumerable.OrderBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(this IEnumerable<dynamic> source, string property)
{
    return Enumerable.OrderByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(this IOrderedEnumerable<dynamic> source, string property)
{
    return Enumerable.ThenBy<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(this IOrderedEnumerable<dynamic> source, string property)
{
    return Enumerable.ThenByDescending<dynamic, object>(source, AccessorCache.GetAccessor(property), Comparer<object>.Default);
}

1
投票

以下单行代码采用通用

IEnumerable<T>
集合并返回分页的:

static class PaginationExtension
{
  internal static IEnumerable<IEnumerable<T>> Paginated<T>(
    this IEnumerable<T> xs,
    int pageSize) =>

    Enumerable.Range(0, (int)Math.Ceiling(decimal.Divide(xs.Length(), pageSize)))
      .Select(i =>
        xs
          .Skip(i * pageSize)
          .Take(pageSize));
}

这是一个显示其用法的单元测试:

[Theory]
[InlineData(25, 100, 4)]
[InlineData(20, 20, 1)]
[InlineData(20, 10, 1)]
[InlineData(20, 1, 1)]
[InlineData(20, 0, 0)]
[InlineData(20, 21, 2)]
void it_paginates_items(int pageSize, int numberOfItems, int expectedPages)
{
    var items = Enumerable.Range(0, numberOfItems);
    
    var pages = items.Paginated(pageSize);

    Assert.Equal(expectedPages, pages.Length());
}

这与 Lukazoid 的答案类似,但可能更简单。


1
投票
public LightDataTable PagerSelection(int pageNumber, int setsPerPage, Func<LightDataRow, bool> prection = null)
{
    this.setsPerPage = setsPerPage;
    this.pageNumber = pageNumber > 0 ? pageNumber - 1 : pageNumber;
    if (!ValidatePagerByPageNumber(pageNumber))
        return this;

    var rowList = rows.Cast<LightDataRow>();
    if (prection != null)
        rowList = rows.Where(prection).ToList();

    if (!rowList.Any())
        return new LightDataTable() { TablePrimaryKey = this.tablePrimaryKey };
    //if (rowList.Count() < (pageNumber * setsPerPage))
    //    return new LightDataTable(new LightDataRowCollection(rowList)) { TablePrimaryKey = this.tablePrimaryKey };

    return new LightDataTable(new LightDataRowCollection(rowList.Skip(this.pageNumber * setsPerPage).Take(setsPerPage).ToList())) { TablePrimaryKey = this.tablePrimaryKey };
}

通常从 1 开始,但在 IList 中从 0 开始。 所以如果你有 152 行,这意味着你有 8 个分页,但在 IList 中你只有 7 个。


0
投票
var results = (medicineInfo.OrderBy(x=>x.id)
                       .Skip((pages -1) * 2)
                       .Take(2));

0
投票

此方法也会枚举分页项目,因此它的占用空间应该很小。

public static IEnumerable<IEnumerable<T>> Page<T>(this IEnumerable<T> source, int size)
{
    using var enumerator = source.GetEnumerator();
    var end = false;

    while (!end)
    {
        yield return enumerate(size);
    }

    IEnumerable<T> enumerate(int s)
    {
        while (s-- > 0)
        {
            end = !enumerator.MoveNext();
            if (end)
                yield break;

            yield return enumerator.Current;
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.