使用 LINQ 对对象进行分页

如何在 LINQ 查询中实现分页? 实际上,就目前而言,如果能够模仿 sql TOP 函数,我将感到满意。但是,我确信对完整分页支持的需求在以后会更快出现。

var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
166848 次浏览

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

See MSDN for more information on how to use these methods: http://msdn.microsoft.com/en-us/library/bb386988.aspx

假设你已经考虑到了 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);

EDIT - Removed Skip(0) as it's not necessary

var queryResult = (from o in objects where ...
select new
{
A = o.a,
B = o.b
}
).Take(10);
   ( for o in objects
where ...
select new
{
A=o.a,
B=o.b
})
.Skip((page-1)*pageSize)
.Take(pageSize)

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

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版本)。

根据您的分页需求,您还可以添加一些其他行为(例如处理负的 pageSizepage值)。下面是如何在查询中使用此扩展方法的示例:

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

不知道这对谁有帮助,但我发现它对我的目的很有用:

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个作者。

下面是我在对对象使用 LINQ 时对分页的执行方法:

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

没有这些垃圾 SkipTake,这将是非常低效的,如果你在多页感兴趣。

我使用这种扩展方法:

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();
}
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);

批量大小显然将是一个整数。这利用了这样一个事实,即整数只是删除小数位。

对于这个响应,我半开玩笑,但是它会按照您的要求执行,并且因为它被延迟了,所以如果您这样做了,您不会遭受巨大的性能损失

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

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

    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个。 希望这能让你明白

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

主要有两种选择:

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

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,它是有用的。

下面的一行程序获取一个通用的 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());
}

This is similar to Lukazoid 的回答, but possibly simpler.