Pagineren met LINQ voor objecten

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 Skipen Take. Skipgaat voorbij de eerste N elementen in het resultaat en geeft de rest terug; Takeretourneert 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 Skipen Takeis 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 Skipen 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 IEnumerableen éé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 pageSizeof pageaf 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 Skipen Takewat 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.

Other episodes