Streng genommen bietet ein Repository eine Sammlungssemantik zum Abrufen/Einfügen von Domänenobjekten. Es bietet eine Abstraktion um Ihre Materialisierungsimplementierung (ORM, hand-rolled, mock), so dass die Konsumenten der Domänenobjekte von diesen Details entkoppelt sind. In der Praxis abstrahiert ein Repository in der Regel den Zugriff auf Entitäten, d.h. auf Domänenobjekte mit Identität und in der Regel einem persistenten Lebenszyklus (in der DDD-Variante bietet ein Repository Zugriff auf Aggregatwurzeln).
Eine minimale Schnittstelle für ein Repository sieht wie folgt aus:
void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);
Obwohl Sie Unterschiede in der Namensgebung und die Hinzufügung von Save/SaveOrUpdate-Semantik sehen werden, ist die obige Darstellung die "reine" Idee. Sie erhalten die ICollection Add/Remove Mitglieder plus einige Finder. Wenn Sie IQueryable nicht verwenden, werden Sie auch Finder-Methoden auf dem Repository wie sehen:
FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();
Bei der Verwendung von IQueryable in diesem Zusammenhang gibt es zwei Probleme. Das erste ist die Möglichkeit, dass Implementierungsdetails in Form der Beziehungen des Domänenobjekts an den Client weitergegeben werden, d. h. Verstöße gegen das Gesetz von Demeter. Das zweite Problem besteht darin, dass das Repository Zuständigkeiten für das Auffinden von Daten erhält, die nicht zum eigentlichen Repository des Domänenobjekts gehören, z. B. das Auffinden von Projektionen, die sich weniger auf das angeforderte Domänenobjekt als auf die zugehörigen Daten beziehen.
Außerdem wird durch die Verwendung von IQueryable das Muster "gebrochen": Ein Repository mit IQueryable kann Zugang zu "Domänenobjekten" bieten, muss es aber nicht. IQueryable gibt dem Client eine Menge Optionen darüber, was bei der Ausführung der Abfrage materialisiert werden soll. Dies ist der Hauptpunkt der Debatte über die Verwendung von IQueryable.
Was skalare Werte betrifft, so sollten Sie kein Repository verwenden, um skalare Werte zurückzugeben. Wenn Sie einen Skalarwert benötigen, würden Sie diesen normalerweise von der Entität selbst erhalten. Wenn sich das ineffizient anhört, ist es das auch, aber je nach Ihren Lastmerkmalen/Anforderungen merken Sie das vielleicht nicht. In Fällen, in denen Sie aus Leistungsgründen oder weil Sie Daten aus vielen Domänenobjekten zusammenführen müssen, alternative Sichten auf ein Domänenobjekt benötigen, haben Sie zwei Möglichkeiten.
1) Verwenden Sie das Repository der Entität, um die angegebenen Entitäten zu finden und in eine reduzierte Ansicht zu projizieren/zu mappen.
2) Erstellen Sie eine Finder-Schnittstelle für die Rückgabe eines neuen Domänentyps, der die benötigte reduzierte Ansicht kapselt. Dies wäre kein Repository, da es keine Sammlungssemantik gäbe, aber es könnte bestehende Repositories unter der Haube verwenden.
Wenn Sie ein "reines" Repository verwenden, um auf persistierte Entitäten zuzugreifen, sollten Sie bedenken, dass Sie einige der Vorteile eines ORM beeinträchtigen. In einer "reinen" Implementierung kann der Client keinen Kontext für die Verwendung des Domänenobjekts bereitstellen, so dass Sie dem Repository nicht sagen können: "Hey, ich werde nur die Eigenschaft customer.Name ändern, also mach dir nicht die Mühe, diese Eager-Load-Referenzen zu holen. Auf der anderen Seite stellt sich die Frage, ob ein Kunde von diesen Dingen wissen sollte. Das ist ein zweischneidiges Schwert.
Was die Verwendung von IQueryable anbelangt, so scheinen die meisten Leute damit einverstanden zu sein, das Muster zu "brechen", um die Vorteile der dynamischen Abfragekomposition zu nutzen, insbesondere für Client-Aufgaben wie Paging/Sortierung. In diesem Fall könnten Sie haben:
Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();
und Sie können dann all diese benutzerdefinierten Finder-Methoden überflüssig machen, die das Repository bei wachsenden Abfrageanforderungen wirklich überladen.