IQueryable <T> zurückgeben oder IQueryable <T> nicht zurückgeben

73

Ich habe eine Repository-Klasse, die meinen LINQ in SQL Data Context umschließt. Die Repository-Klasse ist eine Geschäftsbereichsklasse, die die gesamte Datenebenenlogik (und das Caching usw.) enthält.

Hier ist meine Version 1 meiner Repo-Oberfläche.

public interface ILocationRepository
{
    IList<Location> FindAll();
    IList<Location> FindForState(State state);
    IList<Location> FindForPostCode(string postCode);
}

Um das Paging für FindAll zu handhaben, überlege ich, ob IQueryable <ILocation> anstelle von IList verfügbar gemacht werden soll, um die Benutzeroberfläche für Umstände wie Paging zu vereinfachen.

Was sind die Vor- und Nachteile, um IQueryable aus dem Daten-Repo herauszustellen?

Jede Hilfe wird sehr geschätzt.

CVertex
quelle
8
Wie wäre es mit IEnumerable <T> in der Repository-Oberfläche?
Konstantin Tarkus
@KonstantinTarkus Ich bin nicht für die Verwendung IEnumerable<T>im Repository oder in der BLL vorgesehen, da dies die Leistung beeinträchtigen kann, wenn Sie nicht darauf geachtet und viele foreach-Schleifen für dieselbe Liste erstellt haben. In diesem Fall werden Sie GetEnumerator()viele Male aufrufen .
Wahid Bitar

Antworten:

90

Die Profis; Zusammensetzbarkeit:

  • Anrufer können Filter hinzufügen
  • Anrufer können Paging hinzufügen
  • Anrufer können Sortierung hinzufügen
  • etc

Die Nachteile; Nichtprüfbarkeit:

  • Ihr Repository kann nicht mehr ordnungsgemäß auf Einheiten getestet werden. Sie können sich nicht darauf verlassen, dass a: es funktioniert, b: was es tut;
    • Der Aufrufer könnte eine nicht übersetzbare Funktion hinzufügen (dh keine TSQL-Zuordnung; Unterbrechungen zur Laufzeit).
    • Der Anrufer könnte einen Filter / eine Sortierung hinzufügen, mit der er sich wie ein Hund verhält
  • Da Anrufer erwarten IQueryable<T>, dass sie zusammensetzbar sind, werden nicht zusammensetzbare Implementierungen ausgeschlossen - oder Sie werden gezwungen, einen eigenen Abfrageanbieter für sie zu schreiben
  • Dies bedeutet, dass Sie die DAL nicht optimieren / profilieren können

Für Stabilität, habe ich genommen nicht auszusetzen IQueryable<T>oder Expression<...>auf meine Repositories. Dies bedeutet, dass ich weiß, wie sich das Repository verhält, und dass meine oberen Ebenen Mocks verwenden können, ohne sich Sorgen machen zu müssen, ob das eigentliche Repository dies unterstützt. (Erzwingen von Integrationstests).

Ich benutze immer noch IQueryable<T>etc im Repository - aber nicht über die Grenze. Ich habe hier einige weitere Gedanken zu diesem Thema veröffentlicht . Es ist genauso einfach, Paging-Parameter in die Repository-Oberfläche einzufügen. Sie können sogar Erweiterungsmethoden (auf der Schnittstelle) verwenden, um optionale Paging-Parameter hinzuzufügen , sodass die konkreten Klassen nur eine Methode implementieren müssen, dem Aufrufer jedoch möglicherweise 2 oder 3 Überladungen zur Verfügung stehen.

Marc Gravell
quelle
5
Bei sachgemäßer Verwendung IEnumerable<T>wäre OK - aber hauptsächlich für große Datenmengen. Für reguläre Abfragen würde ich eine geschlossene Menge (Array / Liste / usw.) bevorzugen. Insbesondere verwende ich momentan MVC und möchte, dass der Controller alle Daten
abruft
2
... der Controller, da Sie nicht bewiesen haben, dass er Daten abruft (da er IEnumerable<T>normalerweise mit verzögerter Ausführung verwendet wird). Wenn das Repo IList <T> (oder ähnliches) zurückgibt, wissen Sie, dass Sie die Daten haben.
Marc Gravell
1
@Mark, und .. was ist falsch daran, eine Executin innerhalb eines Unit-Tests auszulösen? Bsp.:Assert.IsTrue(result.Any())
Konstantin Tarkus
2
Mit IQueryable <T> ändert dies die eigentliche Abfrage. Mit IEnumerable <T> weniger - aber dennoch: Das Abrufen von Daten liegt in der Verantwortung des Controllers, nicht der Ansicht.
Marc Gravell
1
Können Sie uns bei Ihrer letzten Aussage zu optionalen Paging-Parametern und 2 oder 3 Überladungen, die dem Anrufer zur Verfügung stehen, auf einen Artikel dazu verweisen?
Shawn Mclean
7

Wie in der vorherigen Antwort erwähnt, können Anrufer durch das Offenlegen von IQueryable mit IQueryable selbst spielen, was gefährlich ist oder werden kann.

Die erste Aufgabe der Geschäftslogik besteht darin, die Integrität Ihrer Datenbank aufrechtzuerhalten.

Sie können IList weiterhin verfügbar machen und Ihre Parameter wie folgt ändern: So machen wir ...

public interface ILocationRepository
{
    IList<Location> FindAll(int start, int size);
    IList<Location> FindForState(State state, int start, int size);
    IList<Location> FindForPostCode(string postCode, int start, int size);
}

Wenn Größe == -1, dann geben Sie alle ...

Alternativer Weg...

Wenn Sie IQueryable weiterhin zurückgeben möchten, können Sie IQueryable of List in Ihren Funktionen zurückgeben. Zum Beispiel ...

public class MyRepository
{
    IQueryable<Location> FindAll()
    {
        List<Location> myLocations = ....;
        return myLocations.AsQueryable<Location>;
        // here Query can only be applied on this
        // subset, not directly to the database
    }
}

Die erste Methode hat einen Vorteil gegenüber dem Speicher, da Sie weniger Daten als alle zurückgeben.

Akash Kava
quelle
1
Das ist nicht so elegant. Auf diese Weise muss CVertex diese Parameter (Start, Größe) zu JEDER von ihm erstellten Repository-Methode hinzufügen - sehr schlechte Programmierpraxis.
Twk
Nun, es ist besser, als die Datenbankschnittstelle freizulegen, die Ihre Daten ändern und verderben kann.
Akash Kava
Es müssen nur die Paging-Parameter dort hinzugefügt werden, wo sie tatsächlich verwendet werden. Und wenn sie verwendet werden, ist es keine verschwendete Mühe.
Frank Schwieterman
2
Autsch, deine zweite Methode wäre ein App-Killer! Es würde alle Ergebnisse in den Speicher ziehen und dann LINQ für sie ausführen. Ich habe nie einen wirklichen Grund gefunden AsQueryable, ehrlich zu sein. Ich bin mir nicht mal sicher, warum es existiert. Es verwandelt Ihre Liste nicht plötzlich in eine IQueryable und ermöglicht es Ihnen, Ausdrucksbäume zu erstellen, die an Ihren Datenspeicher gesendet werden. Worum geht es also?
Chev
1
@AlexFord AsQueryableist nützlich, wenn Sie eine nicht generische Datei IEnumerablevon einer Legacy-API erhalten.
Joshperry
2

Ich empfehle IEnumerablestattdessen zu verwenden IList, damit haben Sie mehr Flexibilität.

Auf diese Weise können Sie von Db nur den Teil der Daten abrufen, den Sie wirklich verwenden werden, ohne dass zusätzliche Arbeit in Ihrem Repository geleistet wird.

Stichprobe:

// Repository
public interface IRepository
{
    IEnumerable<Location> GetLocations();
}

// Controller
public ActionResult Locations(int? page)
{
    return View(repository.GetLocations().AsPagination(page ?? 1, 10);
}

Welches ist super sauber und einfach.

Konstantin Tarkus
quelle
Warum? Was sind die Kompromisse?
Richard
1
IList <Location> FindAll () ist nicht sehr effizient, wenn Sie dem Benutzer eine ausgelagerte Liste anzeigen möchten (sagen wir, 1000 Zeilen werden von db abgerufen, aber nur 10 davon werden angezeigt). Mit IQueryable haben Sie kein solches Problem.
Konstantin Tarkus
Übrigens, wenn Sie Paging-Funktionen implementieren möchten, werfen Sie einen Blick auf vorhandene Hilfsklassen in MvcContrib = codeplex.com/mvccontrib (MvcContrib.Pagination-Namespace)
Konstantin Tarkus
Ich mag es, ausführliche Repository-Methoden zu erstellen. zB GetRecords(int page, int itemsPerPage). Anstatt das Paging an die Controller weiterzuleiten.
Chev