Ich habe einige Artikel über die Vorteile des Erstellens von generischen Repositorys für eine neue App gelesen ( Beispiel ). Die Idee scheint gut zu sein, da ich mit demselben Repository mehrere Dinge für verschiedene Entitätstypen gleichzeitig ausführen kann:
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 Repository 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 hervorragend für Entity Framework und bot praktisch eine 1: 1-Zuordnung der in 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 DbContext?
Ich habe versucht, eine PetaPoco- Version des Repository 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 die grundlegenden Funktionen GetAll, GetById, Add, Update, Delete und Save Methoden und verwenden Sie es 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 Muster für das generische Repository für andere Zwecke als Entity Framework nützlich? Wenn nicht, warum sollte es jemand verwenden, anstatt direkt mit Entity Framework zu arbeiten?
Der ursprüngliche Link entspricht nicht dem Muster, das ich in meinem Beispielcode verwendet habe. Hier ist ein ( aktualisierter Link ).
Antworten:
Das generische Repository ist für Entity Framework sogar unbrauchbar (und meiner Meinung nach auch schlecht). Es bringt keinen zusätzlichen Wert für das, was bereits von bereitgestellt wird
IDbSet<T>
(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-Providers erfordern kann.
Das zweite häufige Argument für vereinfachte Komponententests ist ebenfalls falsch, da das Verspotten von Repository / Set mit speicherinternem Datenspeicher den Linq-Anbieter durch einen anderen mit unterschiedlichen Funktionen ersetzt. Der Linq-to-Entities-Anbieter unterstützt nur einen Teil der Linq-Funktionen - er unterstützt sogar nicht alle auf der
IQueryable<T>
Benutzeroberfläche verfügbaren Methoden . Die gemeinsame Nutzung von Ausdrucksbäumen zwischen Datenzugriffsebene und Geschäftslogikebene verhindert das Fälschen der Datenzugriffsebene - die Abfragelogik muss getrennt werden.Wenn Sie eine starke "generische" Abstraktion wünschen, 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 Datenzugriffsebene unterstützt wird. Dies wird durch das Spezifikationsmuster erledigt. Linq on
IQueryable
ist eine Spezifikation (für die Übersetzung ist jedoch ein Provider erforderlich - oder ein benutzerdefinierter Besucher, der den Ausdrucksbaum in eine Abfrage übersetzt). Sie können jedoch Ihre eigene vereinfachte Version definieren und diese 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.quelle
ISession
die für allgemeine Unit-Testzwecke leicht zu verspotten ist, und ich würde das 'Repository' auch gerne löschen, wenn ich mit EF zusammenarbeite - aber gibt es eine einfachere und unkompliziertere Möglichkeit, dies neu zu erstellen? Etwas ÄhnlichesISession
und dasISessionFactory
,IDbContext
denn soweit ich dasIDbContext
- wenn Sie möchtenIDbContext
, können Sie einfach eines erstellen und es in Ihrem abgeleiteten Kontext implementieren.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 verwendet werden kann, ist bestenfalls problematisch.
Wenn Sie dafür sorgen, dass ein Repository für alle Ihre Objekte direkt funktioniert, geht der Punkt daneben. Datenobjekte werden selten oder nie direkt Geschäftsobjekten zugeordnet. In solchen Situationen ist es weniger sinnvoll, T an Filter zu übergeben. Und wenn Sie so viele Funktionen bereitstellen, können Sie sicher sein, dass Sie nicht alles unterstützen können, sobald ein anderer Anbieter hinzukommt.
quelle
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 sich dies auf den aufrufenden Code auswirkt.
Theoretisch funktioniert das gut. In der Praxis ist die Abstraktion, wie Sie gesehen haben, oft undicht. Die Mechanismen für den Zugriff auf Daten unterscheiden sich von den Mechanismen in einem anderen. In einigen Fällen schreiben Sie den Code am Ende zweimal: einmal in der Business-Schicht und wiederholen ihn in der Datenschicht.
Die effektivste Methode zum Erstellen einer generischen Datenschicht 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 Überschreiben.
[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. Mit dem Erfordernis, zu suchen, zu sortieren und zu filtern, steigt die Komplexität der Datenschicht und Abstraktionen werden undicht (z. B. das Offenlegen von LINQ-Abfragen in der Frage). Sobald Benutzer jedoch ihre eigenen Abfragen stellen können, muss das Kosten-Nutzen-Verhältnis der Datenschicht sorgfältig abgewogen werden.
quelle
In fast allen Fällen lohnt es sich, eine Codeschicht über der Datenbank zu haben. Ich würde im Allgemeinen ein "GetByXXXXX" -Muster in diesem Code bevorzugen - es ermöglicht Ihnen, die dahinter stehenden Abfragen nach Bedarf zu optimieren, während die Benutzeroberfläche frei von Datenschnittstellen-Unordnung bleibt.
Generika auszunutzen ist auf jeden Fall 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 SQL-Abfragen in den 2010er Jahren, die überall gelöscht werden, mit ein wenig zusätzlicher Sicherheit.quelle
Nun, mit dem angegebenen Link kann ich sehen, dass es ein praktischer Wrapper für eine ist
DataServiceContext
, aber weder Code-Manipulationen reduziert noch die Lesbarkeit verbessert. Außerdem ist der Zugang zuDataServiceQuery<T>
behindert, was die Flexibilität von.Where()
und einschränkt.Single()
. Es sind auchAddRange()
keine Alternativen vorgesehen. Es ist auch nichtsDelete(Predicate)
vorgesehen, was nützlich sein könnte (repo.Delete<Person>( p => p.Name=="Joe" );
um Joe-s zu löschen). Etc.Fazit: Eine solche API blockiert die native API und beschränkt sie auf einige einfache Operationen.
quelle
Ich würde Ja sagen". Abhängig von Ihrem Datenmodell kann ein gut entwickeltes generisches Repository die Datenzugriffsebene schlanker, übersichtlicher und wesentlich robuster machen.
Lesen Sie diese Artikelserie (von @ chris-pratt ):
quelle