it-swarm.com.de

Gibt es einen echten Vorteil für das generische Repository?

Ich habe einige Artikel über die Vorteile der Erstellung generischer Repositorys für eine neue App gelesen ( Beispiel ). Die Idee scheint nett zu sein, da ich damit dasselbe Repository verwenden kann, um mehrere Dinge für mehrere verschiedene Entitätstypen gleichzeitig auszuführen:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

Der Rest der Implementierung des Repositorys ist jedoch stark von Linq abhängig:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Dieses Repository-Muster funktionierte fantastisch für Entity Framework und bot eine 1: 1-Zuordnung der auf DbContext/DbSet verfügbaren Methoden. Welchen Vorteil bietet dies angesichts der langsamen Einführung von Linq in andere Datenzugriffstechnologien außerhalb von Entity Framework gegenüber der direkten Arbeit mit dem DbContext?

Ich habe versucht, eine PetaPoco -Version des Repositorys zu schreiben, aber PetaPoco unterstützt Linq Expressions nicht. Daher ist das Erstellen einer generischen IRepository-Schnittstelle so gut wie nutzlos, es sei denn, Sie verwenden sie nur für GetAll, GetById, Add , Aktualisieren, Löschen und Speichern von Methoden und verwenden Sie sie als Basisklasse. Dann müssen Sie bestimmte Repositorys mit speziellen Methoden erstellen, um alle "where" -Klauseln zu behandeln, die ich zuvor als Prädikat übergeben konnte.

Ist das generische Repository-Muster für etwas außerhalb von Entity Framework nützlich? Wenn nicht, warum sollte jemand es überhaupt verwenden, anstatt direkt mit Entity Framework zu arbeiten?


Der ursprüngliche Link spiegelt nicht das Muster wider, das ich in meinem Beispielcode verwendet habe. Hier ist ein ( aktualisierter Link ).

28
Sam

Generisches Repository ist sogar nutzlos (und IMHO auch schlecht) für Entity Framework. Es bringt keinen zusätzlichen Wert zu dem, was bereits von IDbSet<T> Bereitgestellt wird (was übrigens ein generisches Repository ist).

Wie Sie bereits festgestellt haben, ist das Argument, dass das generische Repository durch die Implementierung für andere Datenzugriffstechnologien ersetzt werden kann, ziemlich schwach, da es das Schreiben Ihres eigenen Linq-Anbieters erfordern kann.

Das zweite häufige Argument für vereinfachte Unit-Tests ist auch falsch , da das Verspotten von Repository/Set mit In-Memory-Datenspeicher den Linq-Anbieter durch einen anderen ersetzt, der über andere Funktionen verfügt. Der Linq-to-Entities-Anbieter unterstützt nur einen Teil der Linq-Funktionen - er unterstützt sogar nicht alle Methoden, die auf der IQueryable<T> - Schnittstelle verfügbar sind. Das Teilen von Ausdrucksbäumen zwischen der Datenzugriffsschicht und der Geschäftslogikschicht verhindert das Fälschen der Datenzugriffsschicht - die Abfragelogik muss getrennt werden.

Wenn Sie eine starke "generische" Abstraktion haben möchten, müssen Sie auch andere Muster einbeziehen. In diesem Fall müssen Sie eine abstrakte Abfragesprache verwenden, die vom Repository in eine bestimmte Abfragesprache übersetzt werden kann, die von der verwendeten Datenzugriffsschicht unterstützt wird. Dies wird durch das Spezifikationsmuster behandelt. Linq on IQueryable ist eine Spezifikation (für die Übersetzung ist jedoch ein Anbieter erforderlich - oder ein benutzerdefinierter Besucher, der den Ausdrucksbaum in eine Abfrage übersetzt). Sie können jedoch Ihre eigene vereinfachte Version definieren und verwenden. Beispielsweise verwendet NHibernate die Kriterien-API. Der einfachste Weg ist jedoch die Verwendung eines bestimmten Repositorys mit bestimmten Methoden. Diese Methode ist am einfachsten zu implementieren, am einfachsten zu testen und am einfachsten in Komponententests zu fälschen, da die Abfragelogik vollständig verborgen und hinter der Abstraktion getrennt ist.

36
Ladislav Mrnka

Das Problem ist nicht das Repository-Muster. Es ist eine gute Sache, eine Abstraktion zwischen dem Abrufen von Daten und dem Abrufen von Daten zu haben.

Hier geht es um die Umsetzung. Die Annahme, dass ein beliebiger Ausdruck zum Filtern funktioniert, ist bestenfalls schwierig.

Wenn Sie ein Repository für alle Ihre Objekte direkt zum Laufen bringen, verfehlt dies den Punkt. Datenobjekte werden selten oder nie direkt Geschäftsobjekten zugeordnet. Die Übergabe von T an den Filter ist in diesen Situationen viel weniger sinnvoll. Die Bereitstellung dieser Funktionalität garantiert, dass Sie nicht alles unterstützen können, wenn ein anderer Anbieter hinzukommt.

7
Telastyn

Der Wert einer generischen Datenschicht (ein Repository ist eine bestimmte Art von Datenschicht) ermöglicht es dem Code, den zugrunde liegenden Speichermechanismus zu ändern, ohne dass dies Auswirkungen auf den aufrufenden Code hat.

Theoretisch funktioniert das gut. In der Praxis ist die Abstraktion, wie Sie festgestellt haben, häufig undicht. Die Mechanismen, die für den Zugriff auf Daten in einem verwendet werden, unterscheiden sich von den Mechanismen in einem anderen. In einigen Fällen schreiben Sie den Code zweimal: einmal in der Geschäftsschicht und wiederholen ihn in der Datenschicht.

Der effektivste Weg, eine generische Datenschicht zu erstellen, besteht darin, die verschiedenen Arten von Datenquellen zu kennen, die die Anwendung im Voraus verwendet. Wie Sie gesehen haben, kann die Annahme, dass LINQ oder SQL universell sind, ein Problem sein. Der Versuch, neue Datenspeicher nachzurüsten, führt wahrscheinlich zu einem Umschreiben.

[Bearbeiten: Folgendes hinzugefügt.]

Dies hängt auch davon ab, was die Anwendung von der Datenschicht benötigt. Wenn die Anwendung nur Objekte lädt oder speichert, kann die Datenschicht sehr einfach sein. Wenn die Notwendigkeit zum Suchen, Sortieren und Filtern zunimmt, nimmt die Komplexität der Datenschicht zu und Abstraktionen werden undicht (z. B. das Offenlegen von LINQ-Abfragen in der Frage). Sobald Benutzer ihre eigenen Abfragen stellen können, müssen die Kosten und der Nutzen der Datenschicht sorgfältig abgewogen werden.

2
akton

In fast allen Fällen lohnt es sich, eine Codeschicht über der Datenbank zu haben. Im Allgemeinen würde ich ein "GetByXXXXX" -Muster in diesem Code bevorzugen - damit können Sie die dahinter stehenden Abfragen nach Bedarf optimieren und gleichzeitig die Benutzeroberfläche frei von Unordnung in der Datenschnittstelle halten.

Generika zu nutzen ist definitiv ein faires Spiel - eine Load<T>(int id) Methode zu haben macht jede Menge Sinn. Das Erstellen von Repositorys um LINQ ist jedoch das Äquivalent von 2010 zum Löschen von SQL-Abfragen überall mit ein wenig zusätzlicher Typensicherheit.

1
Wyatt Barnett

Nun, mit dem bereitgestellten Link kann ich sehen, dass es ein praktischer Wrapper für ein DataServiceContext ist, aber weder Code-Manipulationen reduziert noch die Lesbarkeit verbessert. Außerdem ist der Zugriff auf DataServiceQuery<T> Versperrt, was die Flexibilität auf .Where() und .Single() beschränkt. Es werden auch keine AddRange() oder Alternativen bereitgestellt. Es wird auch nicht Delete(Predicate) bereitgestellt, was nützlich sein könnte (repo.Delete<Person>( p => p.Name=="Joe" );, um Joe-s zu löschen). Usw.

Schlussfolgerung: Eine solche API blockiert die native API und beschränkt sie auf einige einfache Vorgänge.

0
aiodintsov