3 Stimmen

ASP.NET MVC2 LINQ - Repository-Muster, wo soll der Paginierungscode platziert werden?

Ich arbeite daran, einen HtmlHelper für die Paginierung hinzuzufügen, bin mir aber unsicher, wo der richtige und/oder vorteilhafteste Ort ist, um bestimmte Teile des Paginierungs codes aus Leistungs- und Wartbarkeitssicht zu platzieren.

Ich bin mir unsicher, ob die Teile Skip(), Take() und Count() der Datenmanipulation von Linq to SQL im Repository oder im Controller platziert werden sollten.

Ich bin mir auch unsicher, ob ihre Reihenfolge und wo sie verwendet werden, die Leistung in irgendeiner Weise beeinflusst.

Wenn sie sich im Repository befinden, würde dies meiner Meinung nach so funktionieren:
1. Ich würde die pageIndex und pageSize als Argumente an die Methode des Repository übergeben, die die Daten aus der Datenbank abruft.
2. Dann die vollständige Datensatz aus der Datenbank abrufen.
3. Dann die Anzahl von TotalItems dieses vollständigen Datensatzes in einer Variable speichern.
4. Dann Skip() und Take() anwenden, damit der Datensatz nur die Seite behält, die ich brauche.
5. Den Teil-Datensatz in der Ansicht als einzelne Seite anzeigen.

Wenn sie sich im Controller befinden, würde dies meiner Meinung nach so funktionieren: 1. Ich würde den vollständigen Datensatz aus dem Repository abrufen und in einer Variablen im Controller speichern. 2. Dann die Anzahl von TotalItems für den vollständigen Datensatz abrufen. 3. Dann Skip() und Take() anwenden, damit der Datensatz nur die Seite behält, die ich brauche. 4. Den Teil-Datensatz in der Ansicht als einzelne Seite anzeigen.

Im Controller (mir ist bewusst, dass ich hier die Seitenzahl falsch erhalte und nicht TotalItems):

Character[] charactersToShow = charactersRepository.GetCharactersByRank(this.PageIndex, this.PageSize);
RankViewModel viewModel = new RankViewModel
{
    Characters = charactersToShow,
    PaginationInfo = new PaginationInfo
    {
        CurrentPage = this.PageIndex,
        ItemsPerPage = this.PageSize,
        TotalItems = charactersToShow.Count()
    }
};

Im Repository:

public Character[] GetCharactersByRank(int PageIndex, int PageSize)
{
    IQueryable characters = (from c in db.Characters
        orderby c.Kill descending
        select new Character {
            CharID = c.CharID,
            CharName = c.CharName,
            Level = c.Level
        });
    characters = PageIndex > 1 ? characters.Skip((PageIndex - 1) * PageSize).Take(PageSize) : characters.Take(PageSize);
    return characters.ToArray();
}

Dieser Code ist ein teilweises Beispiel dafür, wie ich die Skip(), Take() und Count() code im Repository implementiert habe. Ich habe TotalItems nicht tatsächlich abgerufen und zurückgegeben, weil mir dann klar wurde, dass ich nicht wusste, wo ich das richtig platzieren sollte.

Ein Teil des Grundes, warum ich unsicher bin, wo ich diese platzieren soll, ist, dass ich nicht weiß, wie Linq to SQL unter der Oberfläche funktioniert, und daher nicht weiß, wie ich für die Leistung optimieren kann. Noch weiß ich, ob dies in diesem Fall überhaupt ein Problem ist.

Muss es alle Datensätze aus der Datenbank abrufen, wenn man ein .Count() auf dem Linq to SQL durchführt? Muss es separate Abfragen machen, wenn ich ein .Count() mache und später ein .Skip() und .Take() mache? Gibt es mögliche Leistungsprobleme bei der Verwendung von .Count() vor einem .Skip() und .Take()?

Das ist mein erstes Mal, dass ich ein ORM verwende, also weiß ich nicht, was ich erwarten soll. Ich weiß, dass ich die Abfragen, die Linq to SQL ausführt, sehen kann, aber ich denke, dass es besser wäre, jemanden mit Erfahrung in diesem Fall zu hören.

Ich möchte dies genauer verstehen, jeder Einblick wäre geschätzt.

2voto

Marko Punkte 69929

Ich habe eine generische PaginatedList-Klasse in meinem Helpers-Ordner, in dem ich auch andere Hilfsklassen ablegen.

Die PaginatedList stammt direkt aus NerdDinner und sieht so aus.

public class PaginatedList: List
{
    public int PageIndex { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }
    public int TotalPages { get; private set; }

    public PaginatedList(IQueryable source, int pageIndex, int pageSize)
    {
        PageIndex = pageIndex;
        PageSize = pageSize;
        TotalCount = source.Count();
        TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);

        this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
    }

    public bool HasPreviousPage
    {
        get
        {
            return (PageIndex > 0);
        }
    }

    public bool HasNextPage
    {
        get
        {
            return (PageIndex + 1 < TotalPages);
        }
    }
}

2voto

Sgraffite Punkte 1808

Ich habe das auf der NerdDinner-Website gefunden, die Marko oben erwähnt hat, und es hat viele meiner Fragen beantwortet.

Von NerdDinner am unteren Rand von Seite 8:

IQueryable ist ein sehr leistungsstarkes Feature, das eine Vielzahl interessanter Szenarien mit verzögerter Ausführung ermöglicht (wie Paginierung und zusammengesetzte Abfragen). Wie bei allen leistungsstarken Features sollte man darauf achten, wie man es verwendet, und sicherstellen, dass es nicht missbraucht wird.

Es ist wichtig zu erkennen, dass das Zurückgeben eines IQueryable-Ergebnisses aus Ihrem Repository dem aufrufenden Code ermöglicht, verkettete Operatormethoden anzuhängen und somit an der endgültigen Abfrageausführung teilzunehmen. Wenn Sie dem aufrufenden Code diese Möglichkeit nicht geben möchten, sollten Sie IList oder IEnumerable-Ergebnisse zurückgeben - die die Ergebnisse einer bereits ausgeführten Abfrage enthalten.

Für Paginierungsszenarien müssten Sie die eigentliche Datenpaginierungslogik in die aufgerufene Repository-Methode einbauen. In diesem Szenario könnten wir unsere FindUpcomingDinners()-Suchmethode aktualisieren, um eine Signatur zu haben, die entweder eine PaginatedList zurückgab:

PaginatedList< Dinner> FindUpcomingDinners(int pageIndex, int pageSize) { }

Oder eine IList zurückgeben und einen "totalCount" out-Param verwenden, um die Gesamtzahl der Dinners zurückzugeben:

IList FindUpcomingDinners(int pageIndex, int pageSize, out int totalCount) { }

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