120 Stimmen

Paging mit LINQ für Objekte

Wie würden Sie Paging in einer LINQ-Abfrage implementieren? Im Moment würde es mir genügen, wenn die Funktion sql TOP nachgeahmt werden könnte. Ich bin mir jedoch sicher, dass der Bedarf an einer vollständigen Unterstützung für das Paging früher oder später ohnehin auftritt.

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

286voto

David Pfeffer Punkte 37541

Sie suchen nach dem Skip y Take Erweiterungsmethoden. Skip geht an den ersten N Elementen des Ergebnisses vorbei und gibt den Rest zurück; Take gibt die ersten N Elemente des Ergebnisses zurück und lässt alle übrigen Elemente weg.

Weitere Informationen über die Verwendung dieser Methoden finden Sie auf MSDN: http://msdn.microsoft.com/en-us/library/bb386988.aspx

Angenommen, Sie berücksichtigen bereits, dass die pageNumber bei 0 beginnen sollte (Verringerung um 1, wie in den Kommentaren vorgeschlagen) Sie könnten es wie folgt tun:

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

Andernfalls, wenn pageNumber 1-basiert ist (wie von @Alvin vorgeschlagen)

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

7 Stimmen

Sollte ich die gleiche Technik über SQL mit einer großen Datenbank verwenden, wird es die gesamte Tabelle in den Speicher zuerst nehmen und dann die unerwünschten wegwerfen?

2 Stimmen

Wenn Sie daran interessiert sind, was unter der Haube vor sich geht, bieten die meisten LINQ-Datenbanktreiber übrigens eine Möglichkeit, Debug-Ausgabeinformationen für das tatsächlich ausgeführte SQL zu erhalten.

0 Stimmen

Rob Conery hat in einem Blog über eine PagedList<T>-Klasse geschrieben, die Ihnen den Einstieg erleichtern kann. blog.wekeroad.com/blog/aspnet-mvc-pagedlistt

66voto

Tomas Petricek Punkte 233658

Verwendung von Skip y Take ist definitiv der richtige Weg. Wenn ich dies implementieren würde, würde ich wahrscheinlich meine eigene Erweiterungsmethode für das Paging schreiben (um den Code lesbarer zu machen). Die Implementierung kann natürlich Skip y 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);
  }
}

Die Klasse definiert zwei Erweiterungsmethoden - eine für IEnumerable und eine für IQueryable was bedeutet, dass Sie es sowohl mit LINQ to Objects als auch mit LINQ to SQL verwenden können (beim Schreiben von Datenbankabfragen wählt der Compiler die IQueryable Version).

Je nach Ihren Anforderungen an die Auslagerungsfunktion können Sie auch zusätzliches Verhalten hinzufügen (z. B. zur Behandlung negativer pageSize o page Wert). Hier ist ein Beispiel, wie Sie diese Erweiterungsmethode in Ihrer Abfrage verwenden würden:

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

3 Stimmen

Ich glaube, dass dies die gesamte Ergebnismenge zurückgeben wird, und dann Filter im Speicher statt auf dem Server. Enorme Leistung Hit gegen eine Datenbank, wenn dies SQL ist.

1 Stimmen

@jvenema Sie haben Recht. Da dies mit dem IEnumerable Schnittstelle und nicht IQueryable wird die gesamte Datenbanktabelle herangezogen, was zu einem erheblichen Leistungsverlust führt.

2 Stimmen

Sie können natürlich auch einfach eine Überladung für IQueryable damit es auch mit Datenbankabfragen funktioniert (ich habe die Antwort bearbeitet und hinzugefügt). Es ist etwas unglücklich, dass man den Code nicht vollständig generisch schreiben kann (in Haskell wäre dies mit Typklassen möglich). Die ursprüngliche Frage bezog sich auf LINQ to Objects, also habe ich nur eine Überladung geschrieben.

50voto

Lukazoid Punkte 18149

Hier ist mein leistungsfähiger Ansatz zum Paging bei der Verwendung von LINQ to objects:

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

Dies kann dann wie folgt verwendet werden:

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

Nichts von diesem Blödsinn Skip y Take was sehr ineffizient ist, wenn Sie an mehreren Seiten interessiert sind.

1 Stimmen

Es funktioniert in Entity Framework mit Azure SQL Data Warehouse, die nicht unterstützen Skip-Methode (intern mit OFFSET Klausel)

4 Stimmen

Das musste einfach gestohlen und in meine allgemeine Bibliothek aufgenommen werden, danke! Ich habe die Methode einfach umbenannt in Paginate zu entfernen noun gegen verb Zweideutigkeit.

0 Stimmen

Ich frage mich, ob diese Aussage zutrifft. Jetzt, wo es schon ein paar Jahre her ist, haben Skip y Take optimiert worden?

11voto

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

7voto

Bitfiddler Punkte 3652

Ich weiß nicht, ob das jemandem hilft, aber ich fand es für meine Zwecke nützlich:

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

Um dies zu verwenden, würden Sie einige linq Abfrage haben, und übergeben Sie das Ergebnis zusammen mit der Seitengröße in eine foreach-Schleife:

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
}

So wird über jeden Autor iteriert und 100 Autoren auf einmal abgerufen.

0 Stimmen

Da Count() die Sammlung aufzählt, können Sie sie genauso gut in List() umwandeln und mit Indizes iterieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X