Rückgabe eines IQueryable aus einem IRepository

8

Ist es unter Verwendung des Repository-Musters richtig, eine IQueryable eines Datensatzes (einer Tabelle) für die allgemeine Verwendung zurückzugeben?

Dies ist in vielen Fällen sehr praktisch, insbesondere wenn externe Bibliotheken verwendet werden, die diese Schnittstelle nutzen, z. B. einige Plugins, die UI-Elemente sortieren / filtern und an sie binden.

Das Offenlegen eines IQueryable scheint das Design jedoch manchmal fehleranfällig zu machen. Dies kann in Verbindung mit einer falschen Verwendung von Lazy Loading zu schwerwiegenden Leistungseinbußen führen.

Auf der anderen Seite scheint es überflüssig zu sein, Zugriffsmethoden für jede einzelne Verwendung zu haben, und es ist auch viel Arbeit (unter Berücksichtigung von Komponententests usw.).

Mihalis Bagos
quelle
2
IMO: Das Verstecken der Persistenztechnologie (EF, NHibernate) vor Entwicklern ist eine schlechte Idee. Zeigen Sie einfach, was diese Technologie enthüllt.
Euphoric
@Euphoric Ok, aber wenn ich in ASP.NET MVC Controller mit 3000 Leitungen habe, erstellen die Leute gerne, new DbContext()wann SaveChanges()immer sie wollen, und rufen an, wann immer sie wollen. Es ist nicht möglich, etwas zu testen, und ich kann mir nur vorstellen, dass eine zusätzliche Ebene es versteckt.
Mateusz
@Mateusz Das ist kein Problem der Technologie, sondern der Menschen. Sie haben entweder beschissene Entwickler oder beschissenes Management, das diese Entwickler nicht richtig schulen kann.
Euphorisch

Antworten:

12

Mark Seemann hat einen ausgezeichneten Blog-Beitrag zu diesem Thema: IQueryable is Tight Coupling . Er fasst es im letzten Teil gut zusammen (Hervorhebung von mir):

Sie mögen denken, dass dies alles eine theoretische Übung ist, aber es ist tatsächlich wichtig. Beim Schreiben von Clean Code ist es wichtig, eine API so zu gestalten, dass klar ist, was sie tut.

Eine solche Schnittstelle gibt falsche Garantien:

public interface IRepository
{
    IQueryable<T> Query<T>();
}

Nach dem LSP- und Postel-Gesetz scheint es zu garantieren, dass Sie jeden Abfrageausdruck (egal wie komplex) für die zurückgegebene Instanz schreiben können, und es würde immer funktionieren .

In der Praxis wird dies niemals passieren.

Programmierer, die solche Schnittstellen definieren, haben ausnahmslos ein bestimmtes ORM im Auge und neigen implizit dazu, innerhalb der Grenzen zu bleiben, von denen sie wissen, dass sie für dieses bestimmte ORM sicher sind. Dies ist eine undichte Abstraktion.

Wenn Sie ein bestimmtes ORM im Sinn haben, geben Sie dies ausdrücklich an. Verstecke es nicht hinter einer Schnittstelle. Es entsteht die Illusion, dass Sie eine Implementierung durch eine andere ersetzen können. In der Praxis ist das unmöglich. Stellen Sie sich vor, Sie möchten eine Implementierung über einen Ereignisspeicher bereitstellen.

Der Kuchen ist eine Lüge.

ORMs wie Entity Framework sind Implementierungen des Repository und des Unit of Work-Musters. Es ist nicht nötig, sie in eine andere zu wickeln.

Kristof Claes
quelle
Danke für die Antwort. Scheint, dass meine Bedenken denen von Seemann oft entsprechen, aber ich habe mir nicht die Zeit genommen, ihn zu lesen, was ich beabsichtige. Das einzige, mit dem ich nicht einverstanden bin, ist, dass ein Repository - auch wenn es stark gekoppelt ist - sehr nützlich ist, um die Geschäftslogik zu kapseln, selbst wenn es sich um eine einfachere Zugriffsebene handelt (z. B. VisibleItems, DeletedItems) usw., und beim DRY-Konzept zu helfen.
Mihalis Bagos
@Kristof Nur eine Frage zu "Entity Framework sind Implementierungen des Repository und des Unit of Work-Musters". Was kann ich tun, damit die Leute new DbContext()in MVC keine Controller- und komplexe Logik in einer einzigen Methode einfügen und dann auch keine Logik in die Ansichten einfügen? Ich habe Legacy-Code, in dem ich keine Unit-Tests durchführen kann. Um das Testen zu ermöglichen und zu verhindern, dass Leute wirklich schlechten Code machen, benötige ich eine zusätzliche Ebene. Daher muss ich meine eigenen Repositories für Bildungszwecke implementieren.
Mateusz
Es gibt andere Optionen wie Serviceklassen oder Befehls- und Abfrageklassen.
Kristof Claes
1

In diesem Punkt wird es keinen Konsens geben. Meiner Meinung und Erfahrung nach sollte ein Repository Objekte mit bestimmten Verwendungszwecken zurückgeben. Zumindest, wenn Sie das von Eric Evens in DDD definierte Repository-Muster verwenden. Ein Repository ist eine "Brücke" zwischen Geschäftslogik, Persistenz und Fabriken.

Wenn Sie direkter auf die Persistenz zugreifen möchten, suchen Sie möglicherweise nach dem Gateway-Muster.

Nach dem, was Sie hier sagen, möchten Sie diese Exposition gegenüber der Persistenz jedoch verbergen, damit das Proxy-Muster für Sie nützlich sein kann.

Patkos Csaba
quelle
Während die Exposition gegenüber Persistenz kein Hauptanliegen ist, ist zumindest eine Abstraktion auf oberster Ebene für Muster wie Eltern-Kind-Entitäten obligatorisch. Ich werde mich auch mit diesen Mustern befassen, danke!
Mihalis Bagos