Hoe zou u paging in een LINQ-query implementeren?
Eigenlijk zou ik voorlopig al tevreden zijn als de sql TOP-functie nagebootst zou kunnen worden. Ik ben er echter zeker van dat de behoefte aan volledige paging-ondersteuning toch eerder komt.
var queryResult = from o in objects
where ...
select new
{
A = o.a,
B = o.b
}
????????? TOP 10????????
Antwoord 1, autoriteit 100%
U zoekt naar de extensiemethoden Skip
en Take
. Skip
gaat voorbij de eerste N elementen in het resultaat en geeft de rest terug; Take
retourneert de eerste N elementen in het resultaat, waarbij alle resterende elementen worden verwijderd.
Zie MSDN voor meer informatie over het gebruik van deze methoden: http:// msdn.microsoft.com/en-us/library/bb386988.aspx
Ervan uitgaande dat u er al rekening mee houdt dat het paginanummer moet beginnen bij 0 (afname per 1 zoals voorgesteld in de opmerkingen), zou u het als volgt kunnen doen:
int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
.Skip(numberOfObjectsPerPage * pageNumber)
.Take(numberOfObjectsPerPage);
Anders als pageNumber op 1-gebaseerd is (zoals voorgesteld door @Alvin)
int numberOfObjectsPerPage = 10;
var queryResultPage = queryResult
.Skip(numberOfObjectsPerPage * (pageNumber - 1))
.Take(numberOfObjectsPerPage);
Antwoord 2, autoriteit 23%
Het gebruik van Skip
en Take
is absoluut de juiste keuze. Als ik dit zou implementeren, zou ik waarschijnlijk mijn eigen extensiemethode schrijven om paginering af te handelen (om de code leesbaarder te maken). De implementatie kan natuurlijk gebruik maken van Skip
en 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);
}
}
De klasse definieert twee uitbreidingsmethoden – één voor IEnumerable
en één voor IQueryable
, wat betekent dat je het kunt gebruiken met zowel LINQ to Objects als LINQ to SQL (wanneer databasequery schrijft, zal de compiler de IQueryable
-versie kiezen).
Afhankelijk van uw pagingvereisten, kunt u ook wat extra gedrag toevoegen (bijvoorbeeld om een negatieve waarde voor pageSize
of page
af te handelen). Hier is een voorbeeld van hoe u deze extensiemethode zou gebruiken in uw zoekopdracht:
var q = (from p in products
where p.Show == true
select new { p.Name }).Page(10, pageIndex);
Antwoord 3, autoriteit 17%
Hier is mijn performante benadering van paging bij het gebruik van LINQ naar objecten:
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);
}
}
}
Dit kan dan als volgt worden gebruikt:
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
}
}
Geen van deze onzin Skip
en Take
wat zeer inefficiënt zal zijn als je geïnteresseerd bent in meerdere pagina’s.
Antwoord 4, autoriteit 4%
( for o in objects
where ...
select new
{
A=o.a,
B=o.b
})
.Skip((page-1)*pageSize)
.Take(pageSize)
Antwoord 5, autoriteit 3%
Ik weet niet of dit iemand zal helpen, maar ik vond het nuttig voor mijn doeleinden:
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++;
}
}
Om dit te gebruiken zou je een linq-query hebben en het resultaat samen met de paginagrootte in een foreach-lus doorgeven:
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
}
Dus dit herhaalt elke auteur, waarbij 100 auteurs tegelijk worden opgehaald.
Antwoord 6, autoriteit 2%
EDIT – Skip(0) verwijderd omdat het niet nodig is
var queryResult = (from o in objects where ...
select new
{
A = o.a,
B = o.b
}
).Take(10);
Antwoord 7
var pages = items.Select((item, index) => new { item, Page = index / batchSize }).GroupBy(g => g.Page);
Batchgrootte zal uiteraard een geheel getal zijn. Dit maakt gebruik van het feit dat gehele getallen eenvoudig decimalen laten vallen.
Ik maak maar een grapje met deze reactie, maar het zal doen wat je wilt, en omdat het wordt uitgesteld, krijg je geen grote prestatiestraf als je dat doet
pages.First(p => p.Key == thePage)
Deze oplossing is niet voor LinqToEntities, ik weet niet eens of dit een goede query kan worden.
Antwoord 8
Vergelijkbaar met Lukazoid’s antwoordIk heb een extensie gemaakt voor 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);
}
}
}
Het is handig als skip of nemen niet wordt ondersteund.